From 1566879798185efdcfba62d2944a2e60dc10d814 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 13 Oct 2025 19:53:57 +0200 Subject: [PATCH 001/273] clean-up - update file name to match lint name - use `Symbol`s instead of `&str`s - get rid of needless lifetimes --- ...alls.rs => collapsible_span_lint_calls.rs} | 47 +++++++++---------- clippy_lints_internal/src/lib.rs | 6 +-- clippy_utils/src/sym.rs | 4 ++ 3 files changed, 29 insertions(+), 28 deletions(-) rename clippy_lints_internal/src/{collapsible_calls.rs => collapsible_span_lint_calls.rs} (88%) diff --git a/clippy_lints_internal/src/collapsible_calls.rs b/clippy_lints_internal/src/collapsible_span_lint_calls.rs similarity index 88% rename from clippy_lints_internal/src/collapsible_calls.rs rename to clippy_lints_internal/src/collapsible_span_lint_calls.rs index 7c9e7286925e..e83508f6ff4a 100644 --- a/clippy_lints_internal/src/collapsible_calls.rs +++ b/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; -use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt}; +use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt, sym}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -91,8 +91,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { let and_then_snippets = get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span); let mut sle = SpanlessEq::new(cx).deny_side_effects(); - match ps.ident.as_str() { - "span_suggestion" if sle.eq_expr(call_sp, &span_call_args[0]) => { + match ps.ident.name { + sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => { suggest_suggestion( cx, expr, @@ -100,19 +100,19 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { &span_suggestion_snippets(cx, span_call_args), ); }, - "span_help" if sle.eq_expr(call_sp, &span_call_args[0]) => { + sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => { let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true); }, - "span_note" if sle.eq_expr(call_sp, &span_call_args[0]) => { + sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => { let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#); suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true); }, - "help" => { + sym::help => { let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#); suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false); }, - "note" => { + sym::note => { let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#); suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false); }, @@ -122,11 +122,11 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { } } -struct AndThenSnippets<'a> { - cx: Cow<'a, str>, - lint: Cow<'a, str>, - span: Cow<'a, str>, - msg: Cow<'a, str>, +struct AndThenSnippets { + cx: Cow<'static, str>, + lint: Cow<'static, str>, + span: Cow<'static, str>, + msg: Cow<'static, str>, } fn get_and_then_snippets( @@ -135,7 +135,7 @@ fn get_and_then_snippets( lint_span: Span, span_span: Span, msg_span: Span, -) -> AndThenSnippets<'static> { +) -> AndThenSnippets { let cx_snippet = snippet(cx, cx_span, "cx"); let lint_snippet = snippet(cx, lint_span, ".."); let span_snippet = snippet(cx, span_span, "span"); @@ -149,16 +149,13 @@ fn get_and_then_snippets( } } -struct SpanSuggestionSnippets<'a> { - help: Cow<'a, str>, - sugg: Cow<'a, str>, - applicability: Cow<'a, str>, +struct SpanSuggestionSnippets { + help: Cow<'static, str>, + sugg: Cow<'static, str>, + applicability: Cow<'static, str>, } -fn span_suggestion_snippets<'a, 'hir>( - cx: &LateContext<'_>, - span_call_args: &'hir [Expr<'hir>], -) -> SpanSuggestionSnippets<'a> { +fn span_suggestion_snippets<'hir>(cx: &LateContext<'_>, span_call_args: &'hir [Expr<'hir>]) -> SpanSuggestionSnippets { let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); let sugg_snippet = snippet(cx, span_call_args[2].span, ".."); let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable"); @@ -173,8 +170,8 @@ fn span_suggestion_snippets<'a, 'hir>( fn suggest_suggestion( cx: &LateContext<'_>, expr: &Expr<'_>, - and_then_snippets: &AndThenSnippets<'_>, - span_suggestion_snippets: &SpanSuggestionSnippets<'_>, + and_then_snippets: &AndThenSnippets, + span_suggestion_snippets: &SpanSuggestionSnippets, ) { span_lint_and_sugg( cx, @@ -199,7 +196,7 @@ fn suggest_suggestion( fn suggest_help( cx: &LateContext<'_>, expr: &Expr<'_>, - and_then_snippets: &AndThenSnippets<'_>, + and_then_snippets: &AndThenSnippets, help: &str, with_span: bool, ) { @@ -226,7 +223,7 @@ fn suggest_help( fn suggest_note( cx: &LateContext<'_>, expr: &Expr<'_>, - and_then_snippets: &AndThenSnippets<'_>, + and_then_snippets: &AndThenSnippets, note: &str, with_span: bool, ) { diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index 43cde86504f5..2fc172f64725 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -31,7 +31,7 @@ extern crate rustc_session; extern crate rustc_span; mod almost_standard_lint_formulation; -mod collapsible_calls; +mod collapsible_span_lint_calls; mod derive_deserialize_allowing_unknown; mod internal_paths; mod lint_without_lint_pass; @@ -46,7 +46,7 @@ use rustc_lint::{Lint, LintStore}; static LINTS: &[&Lint] = &[ almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION, - collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS, + collapsible_span_lint_calls::COLLAPSIBLE_SPAN_LINT_CALLS, derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN, lint_without_lint_pass::DEFAULT_LINT, lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE, @@ -66,7 +66,7 @@ pub fn register_lints(store: &mut LintStore) { store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths)); store.register_early_pass(|| Box::new(produce_ice::ProduceIce)); - store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls)); + store.register_late_pass(|_| Box::new(collapsible_span_lint_calls::CollapsibleCalls)); store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::::default()); diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 2b22f344e8c0..e3d733cada71 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -173,6 +173,7 @@ generate! { get_unchecked, get_unchecked_mut, has_significant_drop, + help, hidden_glob_reexports, hygiene, insert, @@ -310,7 +311,10 @@ generate! { sort, sort_by, sort_unstable_by, + span_help, span_lint_and_then, + span_note, + span_suggestion, split, split_at, split_at_checked, From 4d795229058b568570cf68a1320cd5bd95eab377 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 13 Oct 2025 19:32:34 +0200 Subject: [PATCH 002/273] fix: use `snippet_with_context` for spans that are likely to contain macro expns --- .../src/collapsible_span_lint_calls.rs | 69 ++++++++++++------- .../collapsible_span_lint_calls.fixed | 9 +++ .../collapsible_span_lint_calls.rs | 29 ++++++++ .../collapsible_span_lint_calls.stderr | 50 +++++++++++++- 4 files changed, 131 insertions(+), 26 deletions(-) diff --git a/clippy_lints_internal/src/collapsible_span_lint_calls.rs b/clippy_lints_internal/src/collapsible_span_lint_calls.rs index e83508f6ff4a..f7adb782929c 100644 --- a/clippy_lints_internal/src/collapsible_span_lint_calls.rs +++ b/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt, sym}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::Span; +use rustc_span::{Span, SyntaxContext}; use std::borrow::{Borrow, Cow}; @@ -88,33 +88,42 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { && let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind && let ExprKind::Path(..) = recv.kind { - let and_then_snippets = - get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span); + let mut app = Applicability::MachineApplicable; + let expr_ctxt = expr.span.ctxt(); + let and_then_snippets = get_and_then_snippets( + cx, + expr_ctxt, + call_cx.span, + call_lint.span, + call_sp.span, + call_msg.span, + &mut app, + ); let mut sle = SpanlessEq::new(cx).deny_side_effects(); match ps.ident.name { sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => { - suggest_suggestion( - cx, - expr, - &and_then_snippets, - &span_suggestion_snippets(cx, span_call_args), - ); + let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app); + suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app); }, sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => { - let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); - suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true); + let help_snippet = + snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0; + suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app); }, sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => { - let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#); - suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true); + let note_snippet = + snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0; + suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app); }, sym::help => { - let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#); - suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false); + let help_snippet = + snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0; + suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app); }, sym::note => { - let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#); - suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false); + let note_snippet = + snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0; + suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app); }, _ => (), } @@ -131,15 +140,17 @@ struct AndThenSnippets { fn get_and_then_snippets( cx: &LateContext<'_>, + expr_ctxt: SyntaxContext, cx_span: Span, lint_span: Span, span_span: Span, msg_span: Span, + app: &mut Applicability, ) -> AndThenSnippets { let cx_snippet = snippet(cx, cx_span, "cx"); let lint_snippet = snippet(cx, lint_span, ".."); let span_snippet = snippet(cx, span_span, "span"); - let msg_snippet = snippet(cx, msg_span, r#""...""#); + let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0; AndThenSnippets { cx: cx_snippet, @@ -155,9 +166,14 @@ struct SpanSuggestionSnippets { applicability: Cow<'static, str>, } -fn span_suggestion_snippets<'hir>(cx: &LateContext<'_>, span_call_args: &'hir [Expr<'hir>]) -> SpanSuggestionSnippets { - let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); - let sugg_snippet = snippet(cx, span_call_args[2].span, ".."); +fn span_suggestion_snippets<'hir>( + cx: &LateContext<'_>, + expr_ctxt: SyntaxContext, + span_call_args: &'hir [Expr<'hir>], + app: &mut Applicability, +) -> SpanSuggestionSnippets { + let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0; + let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0; let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable"); SpanSuggestionSnippets { @@ -172,6 +188,7 @@ fn suggest_suggestion( expr: &Expr<'_>, and_then_snippets: &AndThenSnippets, span_suggestion_snippets: &SpanSuggestionSnippets, + app: Applicability, ) { span_lint_and_sugg( cx, @@ -189,7 +206,7 @@ fn suggest_suggestion( span_suggestion_snippets.sugg, span_suggestion_snippets.applicability ), - Applicability::MachineApplicable, + app, ); } @@ -199,6 +216,7 @@ fn suggest_help( and_then_snippets: &AndThenSnippets, help: &str, with_span: bool, + app: Applicability, ) { let option_span = if with_span { format!("Some({})", and_then_snippets.span) @@ -216,7 +234,7 @@ fn suggest_help( "span_lint_and_help({}, {}, {}, {}, {}, {help})", and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span, ), - Applicability::MachineApplicable, + app, ); } @@ -226,6 +244,7 @@ fn suggest_note( and_then_snippets: &AndThenSnippets, note: &str, with_span: bool, + app: Applicability, ) { let note_span = if with_span { format!("Some({})", and_then_snippets.span) @@ -243,6 +262,6 @@ fn suggest_note( "span_lint_and_note({}, {}, {}, {}, {note_span}, {note})", and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, ), - Applicability::MachineApplicable, + app, ); } diff --git a/tests/ui-internal/collapsible_span_lint_calls.fixed b/tests/ui-internal/collapsible_span_lint_calls.fixed index 76f68686ee2a..2b646a38b534 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.fixed +++ b/tests/ui-internal/collapsible_span_lint_calls.fixed @@ -50,6 +50,15 @@ impl EarlyLintPass for Pass { span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { db.help(help_msg).help(help_msg); }); + + // Issue #15880 + #[expect(clippy::disallowed_names)] + let foo = "foo"; + span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable); + span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}")); + span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}")); + span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}")); + span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}")); } } diff --git a/tests/ui-internal/collapsible_span_lint_calls.rs b/tests/ui-internal/collapsible_span_lint_calls.rs index 214c8783a669..500552370053 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.rs +++ b/tests/ui-internal/collapsible_span_lint_calls.rs @@ -65,6 +65,35 @@ impl EarlyLintPass for Pass { span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { db.help(help_msg).help(help_msg); }); + + // Issue #15880 + #[expect(clippy::disallowed_names)] + let foo = "foo"; + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + //~^ collapsible_span_lint_calls + db.span_suggestion( + expr.span, + format!("try using {foo}"), + format!("{foo}.use"), + Applicability::MachineApplicable, + ); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + //~^ collapsible_span_lint_calls + db.span_help(expr.span, format!("try using {foo}")); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + //~^ collapsible_span_lint_calls + db.help(format!("try using {foo}")); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + //~^ collapsible_span_lint_calls + db.span_note(expr.span, format!("required because of {foo}")); + }); + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + //~^ collapsible_span_lint_calls + db.note(format!("required because of {foo}")); + }); } } diff --git a/tests/ui-internal/collapsible_span_lint_calls.stderr b/tests/ui-internal/collapsible_span_lint_calls.stderr index 9c83538947ca..76b453019270 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.stderr +++ b/tests/ui-internal/collapsible_span_lint_calls.stderr @@ -49,5 +49,53 @@ LL | | db.note(note_msg); LL | | }); | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)` -error: aborting due to 5 previous errors +error: this call is collapsible + --> tests/ui-internal/collapsible_span_lint_calls.rs:72:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | +LL | | db.span_suggestion( +LL | | expr.span, +... | +LL | | ); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable)` + +error: this call is collapsible + --> tests/ui-internal/collapsible_span_lint_calls.rs:81:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | +LL | | db.span_help(expr.span, format!("try using {foo}")); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"))` + +error: this call is collapsible + --> tests/ui-internal/collapsible_span_lint_calls.rs:85:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | +LL | | db.help(format!("try using {foo}")); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"))` + +error: this call is collapsible + --> tests/ui-internal/collapsible_span_lint_calls.rs:89:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | +LL | | db.span_note(expr.span, format!("required because of {foo}")); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"))` + +error: this call is collapsible + --> tests/ui-internal/collapsible_span_lint_calls.rs:93:9 + | +LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { +LL | | +LL | | db.note(format!("required because of {foo}")); +LL | | }); + | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"))` + +error: aborting due to 10 previous errors From b270954300ec06f3478e3758d0c8d3e9f38efebf Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 13 Oct 2025 19:50:47 +0200 Subject: [PATCH 003/273] s/snippet/snippet_with_applicability while we're at it --- .../src/collapsible_span_lint_calls.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clippy_lints_internal/src/collapsible_span_lint_calls.rs b/clippy_lints_internal/src/collapsible_span_lint_calls.rs index f7adb782929c..b048a1004b0d 100644 --- a/clippy_lints_internal/src/collapsible_span_lint_calls.rs +++ b/clippy_lints_internal/src/collapsible_span_lint_calls.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt, sym}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind}; @@ -147,9 +147,9 @@ fn get_and_then_snippets( msg_span: Span, app: &mut Applicability, ) -> AndThenSnippets { - let cx_snippet = snippet(cx, cx_span, "cx"); - let lint_snippet = snippet(cx, lint_span, ".."); - let span_snippet = snippet(cx, span_span, "span"); + let cx_snippet = snippet_with_applicability(cx, cx_span, "cx", app); + let lint_snippet = snippet_with_applicability(cx, lint_span, "..", app); + let span_snippet = snippet_with_applicability(cx, span_span, "span", app); let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0; AndThenSnippets { @@ -174,7 +174,8 @@ fn span_suggestion_snippets<'hir>( ) -> SpanSuggestionSnippets { let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0; let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0; - let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable"); + let applicability_snippet = + snippet_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app); SpanSuggestionSnippets { help: help_snippet, From d160cb7313fb07d85ad57b99a98106211684c270 Mon Sep 17 00:00:00 2001 From: "Ian D. Bollinger" Date: Tue, 4 Nov 2025 13:00:02 -0500 Subject: [PATCH 004/273] `double_comparison`: add missing cases Add checks for expressions such as `x != y && x >= y` and `x != y && x <= y`. --- .../src/operators/double_comparison.rs | 12 +++++++++ tests/ui/double_comparison.fixed | 16 ++++++++++++ tests/ui/double_comparison.rs | 16 ++++++++++++ tests/ui/double_comparison.stderr | 26 ++++++++++++++++++- 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/operators/double_comparison.rs b/clippy_lints/src/operators/double_comparison.rs index 71982023779e..a40a724d2da5 100644 --- a/clippy_lints/src/operators/double_comparison.rs +++ b/clippy_lints/src/operators/double_comparison.rs @@ -39,6 +39,18 @@ pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &E | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { "==" }, + // x != y && x >= y => x > y + (BinOpKind::And, BinOpKind::Ne, BinOpKind::Ge) + // x >= y && x != y => x > y + | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Ne) => { + ">" + }, + // x != y && x <= y => x < y + (BinOpKind::And, BinOpKind::Ne, BinOpKind::Le) + // x <= y && x != y => x < y + | (BinOpKind::And, BinOpKind::Le, BinOpKind::Ne) => { + "<" + }, _ => return, }; diff --git a/tests/ui/double_comparison.fixed b/tests/ui/double_comparison.fixed index 0680eb35ef97..29047b8a31cb 100644 --- a/tests/ui/double_comparison.fixed +++ b/tests/ui/double_comparison.fixed @@ -35,4 +35,20 @@ fn main() { //~^ double_comparisons // do something } + if x < y { + //~^ double_comparisons + // do something + } + if x < y { + //~^ double_comparisons + // do something + } + if x > y { + //~^ double_comparisons + // do something + } + if x > y { + //~^ double_comparisons + // do something + } } diff --git a/tests/ui/double_comparison.rs b/tests/ui/double_comparison.rs index 18ab7d2c4254..13edb2a996a1 100644 --- a/tests/ui/double_comparison.rs +++ b/tests/ui/double_comparison.rs @@ -35,4 +35,20 @@ fn main() { //~^ double_comparisons // do something } + if x != y && x <= y { + //~^ double_comparisons + // do something + } + if x <= y && x != y { + //~^ double_comparisons + // do something + } + if x != y && x >= y { + //~^ double_comparisons + // do something + } + if x >= y && x != y { + //~^ double_comparisons + // do something + } } diff --git a/tests/ui/double_comparison.stderr b/tests/ui/double_comparison.stderr index 984614c203eb..be7eba611cb0 100644 --- a/tests/ui/double_comparison.stderr +++ b/tests/ui/double_comparison.stderr @@ -49,5 +49,29 @@ error: this binary expression can be simplified LL | if x >= y && x <= y { | ^^^^^^^^^^^^^^^^ help: try: `x == y` -error: aborting due to 8 previous errors +error: this binary expression can be simplified + --> tests/ui/double_comparison.rs:38:8 + | +LL | if x != y && x <= y { + | ^^^^^^^^^^^^^^^^ help: try: `x < y` + +error: this binary expression can be simplified + --> tests/ui/double_comparison.rs:42:8 + | +LL | if x <= y && x != y { + | ^^^^^^^^^^^^^^^^ help: try: `x < y` + +error: this binary expression can be simplified + --> tests/ui/double_comparison.rs:46:8 + | +LL | if x != y && x >= y { + | ^^^^^^^^^^^^^^^^ help: try: `x > y` + +error: this binary expression can be simplified + --> tests/ui/double_comparison.rs:50:8 + | +LL | if x >= y && x != y { + | ^^^^^^^^^^^^^^^^ help: try: `x > y` + +error: aborting due to 12 previous errors From 12f41b519435e3952f2bf8e2c829e19c982ace04 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:52:31 +0000 Subject: [PATCH 005/273] More explicit handling of the allocator shim around LTO --- compiler/rustc_codegen_ssa/src/back/write.rs | 56 +++++++++----------- compiler/rustc_codegen_ssa/src/base.rs | 11 +--- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 3e36bd8552b1..885af95b1f2c 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -334,7 +334,6 @@ pub struct CodegenContext { pub output_filenames: Arc, pub invocation_temp: Option, pub module_config: Arc, - pub allocator_config: Arc, pub tm_factory: TargetMachineFactoryFn, pub msvc_imps_needed: bool, pub is_pe_coff: bool, @@ -799,19 +798,12 @@ pub(crate) fn compute_per_cgu_lto_type( sess_lto: &Lto, opts: &config::Options, sess_crate_types: &[CrateType], - module_kind: ModuleKind, ) -> ComputedLtoType { // If the linker does LTO, we don't have to do it. Note that we // keep doing full LTO, if it is requested, as not to break the // assumption that the output will be a single module. let linker_does_lto = opts.cg.linker_plugin_lto.enabled(); - // When we're automatically doing ThinLTO for multi-codegen-unit - // builds we don't actually want to LTO the allocator module if - // it shows up. This is due to various linker shenanigans that - // we'll encounter later. - let is_allocator = module_kind == ModuleKind::Allocator; - // We ignore a request for full crate graph LTO if the crate type // is only an rlib, as there is no full crate graph to process, // that'll happen later. @@ -823,7 +815,7 @@ pub(crate) fn compute_per_cgu_lto_type( let is_rlib = matches!(sess_crate_types, [CrateType::Rlib]); match sess_lto { - Lto::ThinLocal if !linker_does_lto && !is_allocator => ComputedLtoType::Thin, + Lto::ThinLocal if !linker_does_lto => ComputedLtoType::Thin, Lto::Thin if !linker_does_lto && !is_rlib => ComputedLtoType::Thin, Lto::Fat if !is_rlib => ComputedLtoType::Fat, _ => ComputedLtoType::No, @@ -839,23 +831,18 @@ fn execute_optimize_work_item( let dcx = cgcx.create_dcx(); let dcx = dcx.handle(); - let module_config = match module.kind { - ModuleKind::Regular => &cgcx.module_config, - ModuleKind::Allocator => &cgcx.allocator_config, - }; - - B::optimize(cgcx, dcx, &mut module, module_config); + B::optimize(cgcx, dcx, &mut module, &cgcx.module_config); // After we've done the initial round of optimizations we need to // decide whether to synchronously codegen this module or ship it // back to the coordinator thread for further LTO processing (which // has to wait for all the initial modules to be optimized). - let lto_type = compute_per_cgu_lto_type(&cgcx.lto, &cgcx.opts, &cgcx.crate_types, module.kind); + let lto_type = compute_per_cgu_lto_type(&cgcx.lto, &cgcx.opts, &cgcx.crate_types); // If we're doing some form of incremental LTO then we need to be sure to // save our module to disk first. - let bitcode = if module_config.emit_pre_lto_bc { + let bitcode = if cgcx.module_config.emit_pre_lto_bc { let filename = pre_lto_bitcode_filename(&module.name); cgcx.incr_comp_session_dir.as_ref().map(|path| path.join(&filename)) } else { @@ -864,7 +851,7 @@ fn execute_optimize_work_item( match lto_type { ComputedLtoType::No => { - let module = B::codegen(cgcx, module, module_config); + let module = B::codegen(cgcx, module, &cgcx.module_config); WorkItemResult::Finished(module) } ComputedLtoType::Thin => { @@ -1245,7 +1232,7 @@ fn start_executing_work( coordinator_receive: Receiver>, regular_config: Arc, allocator_config: Arc, - allocator_module: Option>, + mut allocator_module: Option>, coordinator_send: Sender>, ) -> thread::JoinHandle> { let sess = tcx.sess; @@ -1303,7 +1290,6 @@ fn start_executing_work( diag_emitter: shared_emitter.clone(), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, - allocator_config, tm_factory: backend.target_machine_factory(tcx.sess, ol, backend_features), msvc_imps_needed: msvc_imps_needed(tcx), is_pe_coff: tcx.sess.target.is_like_windows, @@ -1497,16 +1483,12 @@ fn start_executing_work( let mut llvm_start_time: Option> = None; - let compiled_allocator_module = allocator_module.and_then(|allocator_module| { - match execute_optimize_work_item(&cgcx, allocator_module) { - WorkItemResult::Finished(compiled_module) => return Some(compiled_module), - WorkItemResult::NeedsFatLto(fat_lto_input) => needs_fat_lto.push(fat_lto_input), - WorkItemResult::NeedsThinLto(name, thin_buffer) => { - needs_thin_lto.push((name, thin_buffer)) - } - } - None - }); + if let Some(allocator_module) = &mut allocator_module { + let dcx = cgcx.create_dcx(); + let dcx = dcx.handle(); + + B::optimize(&cgcx, dcx, allocator_module, &allocator_config); + } // Run the message loop while there's still anything that needs message // processing. Note that as soon as codegen is aborted we simply want to @@ -1733,6 +1715,10 @@ fn start_executing_work( assert!(compiled_modules.is_empty()); assert!(needs_thin_lto.is_empty()); + if let Some(allocator_module) = allocator_module.take() { + needs_fat_lto.push(FatLtoInput::InMemory(allocator_module)); + } + // This uses the implicit token let module = do_fat_lto( &cgcx, @@ -1746,6 +1732,13 @@ fn start_executing_work( assert!(compiled_modules.is_empty()); assert!(needs_fat_lto.is_empty()); + if cgcx.lto != Lto::ThinLocal { + if let Some(allocator_module) = allocator_module.take() { + let (name, thin_buffer) = B::prepare_thin(allocator_module); + needs_thin_lto.push((name, thin_buffer)); + } + } + compiled_modules.extend(do_thin_lto( &cgcx, exported_symbols_for_lto, @@ -1762,7 +1755,8 @@ fn start_executing_work( Ok(CompiledModules { modules: compiled_modules, - allocator_module: compiled_allocator_module, + allocator_module: allocator_module + .map(|allocator_module| B::codegen(&cgcx, allocator_module, &allocator_config)), }) }) .expect("failed to spawn coordinator thread"); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 414e9ce1c821..815a3ee706f8 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -49,9 +49,7 @@ use crate::meth::load_vtable; use crate::mir::operand::OperandValue; use crate::mir::place::PlaceRef; use crate::traits::*; -use crate::{ - CachedModuleCodegen, CodegenLintLevels, CrateInfo, ModuleCodegen, ModuleKind, errors, meth, mir, -}; +use crate::{CachedModuleCodegen, CodegenLintLevels, CrateInfo, ModuleCodegen, errors, meth, mir}; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { match (op, signed) { @@ -1132,12 +1130,7 @@ pub fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> // We can re-use either the pre- or the post-thinlto state. If no LTO is // being performed then we can use post-LTO artifacts, otherwise we must // reuse pre-LTO artifacts - match compute_per_cgu_lto_type( - &tcx.sess.lto(), - &tcx.sess.opts, - tcx.crate_types(), - ModuleKind::Regular, - ) { + match compute_per_cgu_lto_type(&tcx.sess.lto(), &tcx.sess.opts, tcx.crate_types()) { ComputedLtoType::No => CguReuse::PostLto, _ => CguReuse::PreLto, } From b93b4b003eb1b1dea681d65a5ac9c222b854046d Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:39:13 +0000 Subject: [PATCH 006/273] Remove opts field from CodegenContext --- compiler/rustc_codegen_gcc/src/back/lto.rs | 2 +- compiler/rustc_codegen_llvm/src/back/lto.rs | 2 +- compiler/rustc_codegen_llvm/src/back/write.rs | 6 +++--- compiler/rustc_codegen_ssa/src/back/lto.rs | 6 +++--- compiler/rustc_codegen_ssa/src/back/write.rs | 14 +++++++++----- compiler/rustc_codegen_ssa/src/base.rs | 6 +++++- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs index 404064fb7a06..08c36d2b7318 100644 --- a/compiler/rustc_codegen_gcc/src/back/lto.rs +++ b/compiler/rustc_codegen_gcc/src/back/lto.rs @@ -290,7 +290,7 @@ pub(crate) fn run_thin( let dcx = cgcx.create_dcx(); let dcx = dcx.handle(); let lto_data = prepare_lto(cgcx, each_linked_rlib_for_lto, dcx); - if cgcx.opts.cg.linker_plugin_lto.enabled() { + if cgcx.use_linker_plugin_lto { unreachable!( "We should never reach this case if the LTO step \ is deferred to the linker" diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index b820b992105f..3137ed288fa8 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -179,7 +179,7 @@ pub(crate) fn run_thin( prepare_lto(cgcx, exported_symbols_for_lto, each_linked_rlib_for_lto, dcx); let symbols_below_threshold = symbols_below_threshold.iter().map(|c| c.as_ptr()).collect::>(); - if cgcx.opts.cg.linker_plugin_lto.enabled() { + if cgcx.use_linker_plugin_lto { unreachable!( "We should never reach this case if the LTO step \ is deferred to the linker" diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 95539059653b..a49d4c010d67 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -398,7 +398,7 @@ impl<'a> DiagnosticHandlers<'a> { }) .and_then(|dir| dir.to_str().and_then(|p| CString::new(p).ok())); - let pgo_available = cgcx.opts.cg.profile_use.is_some(); + let pgo_available = cgcx.module_config.pgo_use.is_some(); let data = Box::into_raw(Box::new((cgcx, dcx))); unsafe { let old_handler = llvm::LLVMRustContextGetDiagnosticHandler(llcx); @@ -738,7 +738,7 @@ pub(crate) unsafe fn llvm_optimize( &*module.module_llvm.tm.raw(), to_pass_builder_opt_level(opt_level), opt_stage, - cgcx.opts.cg.linker_plugin_lto.enabled(), + cgcx.use_linker_plugin_lto, config.no_prepopulate_passes, config.verify_llvm_ir, config.lint_llvm_ir, @@ -801,7 +801,7 @@ pub(crate) fn optimize( let opt_stage = match cgcx.lto { Lto::Fat => llvm::OptStage::PreLinkFatLTO, Lto::Thin | Lto::ThinLocal => llvm::OptStage::PreLinkThinLTO, - _ if cgcx.opts.cg.linker_plugin_lto.enabled() => llvm::OptStage::PreLinkThinLTO, + _ if cgcx.use_linker_plugin_lto => llvm::OptStage::PreLinkThinLTO, _ => llvm::OptStage::PreLinkNoLTO, }; diff --git a/compiler/rustc_codegen_ssa/src/back/lto.rs b/compiler/rustc_codegen_ssa/src/back/lto.rs index e6df6a2469f3..e3dc985bf782 100644 --- a/compiler/rustc_codegen_ssa/src/back/lto.rs +++ b/compiler/rustc_codegen_ssa/src/back/lto.rs @@ -137,15 +137,15 @@ pub(super) fn check_lto_allowed(cgcx: &CodegenContext if !crate_type_allows_lto(*crate_type) { dcx.handle().emit_fatal(LtoDisallowed); } else if *crate_type == CrateType::Dylib { - if !cgcx.opts.unstable_opts.dylib_lto { + if !cgcx.dylib_lto { dcx.handle().emit_fatal(LtoDylib); } - } else if *crate_type == CrateType::ProcMacro && !cgcx.opts.unstable_opts.dylib_lto { + } else if *crate_type == CrateType::ProcMacro && !cgcx.dylib_lto { dcx.handle().emit_fatal(LtoProcMacro); } } - if cgcx.opts.cg.prefer_dynamic && !cgcx.opts.unstable_opts.dylib_lto { + if cgcx.prefer_dynamic && !cgcx.dylib_lto { dcx.handle().emit_fatal(DynamicLinkingWithLTO); } } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 885af95b1f2c..bc7c32931613 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -326,10 +326,12 @@ pub struct CodegenContext { // Resources needed when running LTO pub prof: SelfProfilerRef, pub lto: Lto, + pub use_linker_plugin_lto: bool, + pub dylib_lto: bool, + pub prefer_dynamic: bool, pub save_temps: bool, pub fewer_names: bool, pub time_trace: bool, - pub opts: Arc, pub crate_types: Vec, pub output_filenames: Arc, pub invocation_temp: Option, @@ -796,13 +798,12 @@ pub(crate) enum ComputedLtoType { pub(crate) fn compute_per_cgu_lto_type( sess_lto: &Lto, - opts: &config::Options, + linker_does_lto: bool, sess_crate_types: &[CrateType], ) -> ComputedLtoType { // If the linker does LTO, we don't have to do it. Note that we // keep doing full LTO, if it is requested, as not to break the // assumption that the output will be a single module. - let linker_does_lto = opts.cg.linker_plugin_lto.enabled(); // We ignore a request for full crate graph LTO if the crate type // is only an rlib, as there is no full crate graph to process, @@ -838,7 +839,8 @@ fn execute_optimize_work_item( // back to the coordinator thread for further LTO processing (which // has to wait for all the initial modules to be optimized). - let lto_type = compute_per_cgu_lto_type(&cgcx.lto, &cgcx.opts, &cgcx.crate_types); + let lto_type = + compute_per_cgu_lto_type(&cgcx.lto, cgcx.use_linker_plugin_lto, &cgcx.crate_types); // If we're doing some form of incremental LTO then we need to be sure to // save our module to disk first. @@ -1279,10 +1281,12 @@ fn start_executing_work( let cgcx = CodegenContext:: { crate_types: tcx.crate_types().to_vec(), lto: sess.lto(), + use_linker_plugin_lto: sess.opts.cg.linker_plugin_lto.enabled(), + dylib_lto: sess.opts.unstable_opts.dylib_lto, + prefer_dynamic: sess.opts.cg.prefer_dynamic, fewer_names: sess.fewer_names(), save_temps: sess.opts.cg.save_temps, time_trace: sess.opts.unstable_opts.llvm_time_trace, - opts: Arc::new(sess.opts.clone()), prof: sess.prof.clone(), remark: sess.opts.cg.remark.clone(), remark_dir, diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 815a3ee706f8..2943df8f02aa 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -1130,7 +1130,11 @@ pub fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> // We can re-use either the pre- or the post-thinlto state. If no LTO is // being performed then we can use post-LTO artifacts, otherwise we must // reuse pre-LTO artifacts - match compute_per_cgu_lto_type(&tcx.sess.lto(), &tcx.sess.opts, tcx.crate_types()) { + match compute_per_cgu_lto_type( + &tcx.sess.lto(), + tcx.sess.opts.cg.linker_plugin_lto.enabled(), + tcx.crate_types(), + ) { ComputedLtoType::No => CguReuse::PostLto, _ => CguReuse::PreLto, } From 2d7c571391e558a55767dc9aaeca6989a63c77c4 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:08:09 +0000 Subject: [PATCH 007/273] Remove SharedEmitter from CodegenContext --- compiler/rustc_codegen_gcc/src/back/lto.rs | 12 +- compiler/rustc_codegen_gcc/src/back/write.rs | 8 +- compiler/rustc_codegen_gcc/src/lib.rs | 14 ++- compiler/rustc_codegen_llvm/src/back/lto.rs | 27 +++-- compiler/rustc_codegen_llvm/src/back/write.rs | 43 ++++--- compiler/rustc_codegen_llvm/src/lib.rs | 29 +++-- compiler/rustc_codegen_ssa/src/back/lto.rs | 8 +- compiler/rustc_codegen_ssa/src/back/write.rs | 112 +++++++++++------- .../rustc_codegen_ssa/src/traits/write.rs | 8 +- 9 files changed, 165 insertions(+), 96 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs index 08c36d2b7318..24be3ee4c34d 100644 --- a/compiler/rustc_codegen_gcc/src/back/lto.rs +++ b/compiler/rustc_codegen_gcc/src/back/lto.rs @@ -26,11 +26,11 @@ use std::sync::atomic::Ordering; use gccjit::{Context, OutputKind}; use object::read::archive::ArchiveFile; use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule, ThinShared}; -use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput}; +use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, SharedEmitter}; use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{ModuleCodegen, ModuleKind, looks_like_rust_object_file}; use rustc_data_structures::memmap::Mmap; -use rustc_errors::DiagCtxtHandle; +use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_log::tracing::info; use rustc_middle::bug; use rustc_middle::dep_graph::WorkProduct; @@ -112,10 +112,11 @@ fn save_as_file(obj: &[u8], path: &Path) -> Result<(), LtoBitcodeFromRlib> { /// for further optimization. pub(crate) fn run_fat( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> ModuleCodegen { - let dcx = cgcx.create_dcx(); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); let dcx = dcx.handle(); let lto_data = prepare_lto(cgcx, each_linked_rlib_for_lto, dcx); /*let symbols_below_threshold = @@ -283,12 +284,11 @@ impl ModuleBufferMethods for ModuleBuffer { /// can simply be copied over from the incr. comp. cache. pub(crate) fn run_thin( cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, each_linked_rlib_for_lto: &[PathBuf], modules: Vec<(String, ThinBuffer)>, cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> (Vec>, Vec) { - let dcx = cgcx.create_dcx(); - let dcx = dcx.handle(); let lto_data = prepare_lto(cgcx, each_linked_rlib_for_lto, dcx); if cgcx.use_linker_plugin_lto { unreachable!( @@ -522,8 +522,6 @@ pub fn optimize_thin_module( thin_module: ThinModule, _cgcx: &CodegenContext, ) -> ModuleCodegen { - //let dcx = cgcx.create_dcx(); - //let module_name = &thin_module.shared.module_names[thin_module.idx]; /*let tm_factory_config = TargetMachineFactoryConfig::new(cgcx, module_name.to_str().unwrap()); let tm = (cgcx.tm_factory)(tm_factory_config).map_err(|e| write::llvm_err(&dcx, e))?;*/ diff --git a/compiler/rustc_codegen_gcc/src/back/write.rs b/compiler/rustc_codegen_gcc/src/back/write.rs index eae0f2aa00f6..b6223c5be370 100644 --- a/compiler/rustc_codegen_gcc/src/back/write.rs +++ b/compiler/rustc_codegen_gcc/src/back/write.rs @@ -2,8 +2,11 @@ use std::{env, fs}; use gccjit::{Context, OutputKind}; use rustc_codegen_ssa::back::link::ensure_removed; -use rustc_codegen_ssa::back::write::{BitcodeSection, CodegenContext, EmitObj, ModuleConfig}; +use rustc_codegen_ssa::back::write::{ + BitcodeSection, CodegenContext, EmitObj, ModuleConfig, SharedEmitter, +}; use rustc_codegen_ssa::{CompiledModule, ModuleCodegen}; +use rustc_errors::DiagCtxt; use rustc_fs_util::link_or_copy; use rustc_log::tracing::debug; use rustc_session::config::OutputType; @@ -15,10 +18,11 @@ use crate::{GccCodegenBackend, GccContext, LtoMode}; pub(crate) fn codegen( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, module: ModuleCodegen, config: &ModuleConfig, ) -> CompiledModule { - let dcx = cgcx.create_dcx(); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); let dcx = dcx.handle(); let _timer = cgcx.prof.generic_activity_with_arg("GCC_module_codegen", &*module.name); diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 409b7886740a..5a2ec0a2fc68 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -82,7 +82,7 @@ use gccjit::{TargetInfo, Version}; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule}; use rustc_codegen_ssa::back::write::{ - CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryFn, + CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter, TargetMachineFactoryFn, }; use rustc_codegen_ssa::base::codegen_crate; use rustc_codegen_ssa::target_features::cfg_target_feature; @@ -371,23 +371,25 @@ impl WriteBackendMethods for GccCodegenBackend { fn run_and_optimize_fat_lto( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, // FIXME(bjorn3): Limit LTO exports to these symbols _exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> ModuleCodegen { - back::lto::run_fat(cgcx, each_linked_rlib_for_lto, modules) + back::lto::run_fat(cgcx, shared_emitter, each_linked_rlib_for_lto, modules) } fn run_thin_lto( cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, // FIXME(bjorn3): Limit LTO exports to these symbols _exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec<(String, Self::ThinBuffer)>, cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> (Vec>, Vec) { - back::lto::run_thin(cgcx, each_linked_rlib_for_lto, modules, cached_modules) + back::lto::run_thin(cgcx, dcx, each_linked_rlib_for_lto, modules, cached_modules) } fn print_pass_timings(&self) { @@ -400,7 +402,7 @@ impl WriteBackendMethods for GccCodegenBackend { fn optimize( _cgcx: &CodegenContext, - _dcx: DiagCtxtHandle<'_>, + _shared_emitter: &SharedEmitter, module: &mut ModuleCodegen, config: &ModuleConfig, ) { @@ -409,6 +411,7 @@ impl WriteBackendMethods for GccCodegenBackend { fn optimize_thin( cgcx: &CodegenContext, + _shared_emitter: &SharedEmitter, thin: ThinModule, ) -> ModuleCodegen { back::lto::optimize_thin_module(thin, cgcx) @@ -416,10 +419,11 @@ impl WriteBackendMethods for GccCodegenBackend { fn codegen( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, module: ModuleCodegen, config: &ModuleConfig, ) -> CompiledModule { - back::write::codegen(cgcx, module, config) + back::write::codegen(cgcx, shared_emitter, module, config) } fn prepare_thin(module: ModuleCodegen) -> (String, Self::ThinBuffer) { diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index 3137ed288fa8..3210afc063a9 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -9,12 +9,12 @@ use std::{io, iter, slice}; use object::read::archive::ArchiveFile; use object::{Object, ObjectSection}; use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule, ThinShared}; -use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput}; +use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, SharedEmitter}; use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{ModuleCodegen, ModuleKind, looks_like_rust_object_file}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::memmap::Mmap; -use rustc_errors::DiagCtxtHandle; +use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_hir::attrs::SanitizerSet; use rustc_middle::bug; use rustc_middle::dep_graph::WorkProduct; @@ -150,17 +150,18 @@ fn get_bitcode_slice_from_object_data<'a>( /// for further optimization. pub(crate) fn run_fat( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> ModuleCodegen { - let dcx = cgcx.create_dcx(); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); let dcx = dcx.handle(); let (symbols_below_threshold, upstream_modules) = prepare_lto(cgcx, exported_symbols_for_lto, each_linked_rlib_for_lto, dcx); let symbols_below_threshold = symbols_below_threshold.iter().map(|c| c.as_ptr()).collect::>(); - fat_lto(cgcx, dcx, modules, upstream_modules, &symbols_below_threshold) + fat_lto(cgcx, dcx, shared_emitter, modules, upstream_modules, &symbols_below_threshold) } /// Performs thin LTO by performing necessary global analysis and returning two @@ -168,13 +169,12 @@ pub(crate) fn run_fat( /// can simply be copied over from the incr. comp. cache. pub(crate) fn run_thin( cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec<(String, ThinBuffer)>, cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> (Vec>, Vec) { - let dcx = cgcx.create_dcx(); - let dcx = dcx.handle(); let (symbols_below_threshold, upstream_modules) = prepare_lto(cgcx, exported_symbols_for_lto, each_linked_rlib_for_lto, dcx); let symbols_below_threshold = @@ -197,6 +197,7 @@ pub(crate) fn prepare_thin(module: ModuleCodegen) -> (String, ThinBu fn fat_lto( cgcx: &CodegenContext, dcx: DiagCtxtHandle<'_>, + shared_emitter: &SharedEmitter, modules: Vec>, mut serialized_modules: Vec<(SerializedModule, CString)>, symbols_below_threshold: &[*const libc::c_char], @@ -265,8 +266,13 @@ fn fat_lto( // The linking steps below may produce errors and diagnostics within LLVM // which we'd like to handle and print, so set up our diagnostic handlers // (which get unregistered when they go out of scope below). - let _handler = - DiagnosticHandlers::new(cgcx, dcx, llcx, &module, CodegenDiagnosticsStage::LTO); + let _handler = DiagnosticHandlers::new( + cgcx, + shared_emitter, + llcx, + &module, + CodegenDiagnosticsStage::LTO, + ); // For all other modules we codegened we'll need to link them into our own // bitcode. All modules were codegened in their own LLVM context, however, @@ -730,10 +736,11 @@ impl Drop for ThinBuffer { } pub(crate) fn optimize_thin_module( - thin_module: ThinModule, cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, + thin_module: ThinModule, ) -> ModuleCodegen { - let dcx = cgcx.create_dcx(); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); let dcx = dcx.handle(); let module_name = &thin_module.shared.module_names[thin_module.idx]; diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index a49d4c010d67..a5b6ea08a66d 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -9,7 +9,7 @@ use libc::{c_char, c_int, c_void, size_t}; use rustc_codegen_ssa::back::link::ensure_removed; use rustc_codegen_ssa::back::versioned_llvm_target; use rustc_codegen_ssa::back::write::{ - BitcodeSection, CodegenContext, EmitObj, InlineAsmError, ModuleConfig, + BitcodeSection, CodegenContext, EmitObj, InlineAsmError, ModuleConfig, SharedEmitter, TargetMachineFactoryConfig, TargetMachineFactoryFn, }; use rustc_codegen_ssa::base::wants_wasm_eh; @@ -17,7 +17,7 @@ use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind}; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::small_c_str::SmallCStr; -use rustc_errors::{DiagCtxtHandle, Level}; +use rustc_errors::{DiagCtxt, DiagCtxtHandle, Level}; use rustc_fs_util::{link_or_copy, path_to_c_string}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; @@ -356,7 +356,7 @@ pub(crate) enum CodegenDiagnosticsStage { } pub(crate) struct DiagnosticHandlers<'a> { - data: *mut (&'a CodegenContext, DiagCtxtHandle<'a>), + data: *mut (&'a CodegenContext, &'a SharedEmitter), llcx: &'a llvm::Context, old_handler: Option<&'a llvm::DiagnosticHandler>, } @@ -364,7 +364,7 @@ pub(crate) struct DiagnosticHandlers<'a> { impl<'a> DiagnosticHandlers<'a> { pub(crate) fn new( cgcx: &'a CodegenContext, - dcx: DiagCtxtHandle<'a>, + shared_emitter: &'a SharedEmitter, llcx: &'a llvm::Context, module: &ModuleCodegen, stage: CodegenDiagnosticsStage, @@ -399,7 +399,7 @@ impl<'a> DiagnosticHandlers<'a> { .and_then(|dir| dir.to_str().and_then(|p| CString::new(p).ok())); let pgo_available = cgcx.module_config.pgo_use.is_some(); - let data = Box::into_raw(Box::new((cgcx, dcx))); + let data = Box::into_raw(Box::new((cgcx, shared_emitter))); unsafe { let old_handler = llvm::LLVMRustContextGetDiagnosticHandler(llcx); llvm::LLVMRustContextConfigureDiagnosticHandler( @@ -461,12 +461,16 @@ unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void if user.is_null() { return; } - let (cgcx, dcx) = - unsafe { *(user as *const (&CodegenContext, DiagCtxtHandle<'_>)) }; + let (cgcx, shared_emitter) = + unsafe { *(user as *const (&CodegenContext, &SharedEmitter)) }; + + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); + let dcx = dcx.handle(); match unsafe { llvm::diagnostic::Diagnostic::unpack(info) } { llvm::diagnostic::InlineAsm(inline) => { - cgcx.diag_emitter.inline_asm_error(report_inline_asm( + // FIXME use dcx + shared_emitter.inline_asm_error(report_inline_asm( cgcx, inline.message, inline.level, @@ -777,14 +781,18 @@ pub(crate) unsafe fn llvm_optimize( // Unsafe due to LLVM calls. pub(crate) fn optimize( cgcx: &CodegenContext, - dcx: DiagCtxtHandle<'_>, + shared_emitter: &SharedEmitter, module: &mut ModuleCodegen, config: &ModuleConfig, ) { let _timer = cgcx.prof.generic_activity_with_arg("LLVM_module_optimize", &*module.name); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); + let dcx = dcx.handle(); + let llcx = &*module.module_llvm.llcx; - let _handlers = DiagnosticHandlers::new(cgcx, dcx, llcx, module, CodegenDiagnosticsStage::Opt); + let _handlers = + DiagnosticHandlers::new(cgcx, shared_emitter, llcx, module, CodegenDiagnosticsStage::Opt); if config.emit_no_opt_bc { let out = cgcx.output_filenames.temp_path_ext_for_cgu( @@ -865,19 +873,26 @@ pub(crate) fn optimize( pub(crate) fn codegen( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, module: ModuleCodegen, config: &ModuleConfig, ) -> CompiledModule { - let dcx = cgcx.create_dcx(); + let _timer = cgcx.prof.generic_activity_with_arg("LLVM_module_codegen", &*module.name); + + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); let dcx = dcx.handle(); - let _timer = cgcx.prof.generic_activity_with_arg("LLVM_module_codegen", &*module.name); { let llmod = module.module_llvm.llmod(); let llcx = &*module.module_llvm.llcx; let tm = &*module.module_llvm.tm; - let _handlers = - DiagnosticHandlers::new(cgcx, dcx, llcx, &module, CodegenDiagnosticsStage::Codegen); + let _handlers = DiagnosticHandlers::new( + cgcx, + shared_emitter, + llcx, + &module, + CodegenDiagnosticsStage::Codegen, + ); if cgcx.msvc_imps_needed { create_msvc_imps(cgcx, llcx, llmod); diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 1b65a133d58c..3901662442e9 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -30,12 +30,13 @@ use llvm_util::target_config; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule}; use rustc_codegen_ssa::back::write::{ - CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryConfig, TargetMachineFactoryFn, + CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter, TargetMachineFactoryConfig, + TargetMachineFactoryFn, }; use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{CodegenResults, CompiledModule, ModuleCodegen, TargetConfig}; use rustc_data_structures::fx::FxIndexMap; -use rustc_errors::DiagCtxtHandle; +use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::ty::TyCtxt; @@ -166,14 +167,20 @@ impl WriteBackendMethods for LlvmCodegenBackend { } fn run_and_optimize_fat_lto( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> ModuleCodegen { - let mut module = - back::lto::run_fat(cgcx, exported_symbols_for_lto, each_linked_rlib_for_lto, modules); + let mut module = back::lto::run_fat( + cgcx, + shared_emitter, + exported_symbols_for_lto, + each_linked_rlib_for_lto, + modules, + ); - let dcx = cgcx.create_dcx(); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); let dcx = dcx.handle(); back::lto::run_pass_manager(cgcx, dcx, &mut module, false); @@ -181,6 +188,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { } fn run_thin_lto( cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec<(String, Self::ThinBuffer)>, @@ -188,6 +196,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { ) -> (Vec>, Vec) { back::lto::run_thin( cgcx, + dcx, exported_symbols_for_lto, each_linked_rlib_for_lto, modules, @@ -196,24 +205,26 @@ impl WriteBackendMethods for LlvmCodegenBackend { } fn optimize( cgcx: &CodegenContext, - dcx: DiagCtxtHandle<'_>, + shared_emitter: &SharedEmitter, module: &mut ModuleCodegen, config: &ModuleConfig, ) { - back::write::optimize(cgcx, dcx, module, config) + back::write::optimize(cgcx, shared_emitter, module, config) } fn optimize_thin( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, thin: ThinModule, ) -> ModuleCodegen { - back::lto::optimize_thin_module(thin, cgcx) + back::lto::optimize_thin_module(cgcx, shared_emitter, thin) } fn codegen( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, module: ModuleCodegen, config: &ModuleConfig, ) -> CompiledModule { - back::write::codegen(cgcx, module, config) + back::write::codegen(cgcx, shared_emitter, module, config) } fn prepare_thin(module: ModuleCodegen) -> (String, Self::ThinBuffer) { back::lto::prepare_thin(module) diff --git a/compiler/rustc_codegen_ssa/src/back/lto.rs b/compiler/rustc_codegen_ssa/src/back/lto.rs index e3dc985bf782..ef4c193c4c2a 100644 --- a/compiler/rustc_codegen_ssa/src/back/lto.rs +++ b/compiler/rustc_codegen_ssa/src/back/lto.rs @@ -2,6 +2,7 @@ use std::ffi::CString; use std::sync::Arc; use rustc_data_structures::memmap::Mmap; +use rustc_errors::DiagCtxtHandle; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportLevel}; use rustc_middle::ty::TyCtxt; @@ -124,14 +125,15 @@ pub(super) fn exported_symbols_for_lto( symbols_below_threshold } -pub(super) fn check_lto_allowed(cgcx: &CodegenContext) { +pub(super) fn check_lto_allowed( + cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, +) { if cgcx.lto == Lto::ThinLocal { // Crate local LTO is always allowed return; } - let dcx = cgcx.create_dcx(); - // Make sure we actually can run LTO for crate_type in cgcx.crate_types.iter() { if !crate_type_allows_lto(*crate_type) { diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index bc7c32931613..76f76c84dbdc 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -15,8 +15,8 @@ use rustc_data_structures::profiling::{SelfProfilerRef, VerboseTimingGuard}; use rustc_errors::emitter::Emitter; use rustc_errors::translation::Translator; use rustc_errors::{ - Diag, DiagArgMap, DiagCtxt, DiagMessage, ErrCode, FatalError, FatalErrorMarker, Level, - MultiSpan, Style, Suggestions, + Diag, DiagArgMap, DiagCtxt, DiagCtxtHandle, DiagMessage, ErrCode, FatalError, FatalErrorMarker, + Level, MultiSpan, Style, Suggestions, }; use rustc_fs_util::link_or_copy; use rustc_incremental::{ @@ -348,8 +348,6 @@ pub struct CodegenContext { pub split_dwarf_kind: rustc_session::config::SplitDwarfKind, pub pointer_size: Size, - /// Emitter to use for diagnostics produced during codegen. - pub diag_emitter: SharedEmitter, /// LLVM optimizations for which we want to print remarks. pub remark: Passes, /// Directory into which should the LLVM optimization remarks be written. @@ -364,14 +362,9 @@ pub struct CodegenContext { pub parallel: bool, } -impl CodegenContext { - pub fn create_dcx(&self) -> DiagCtxt { - DiagCtxt::new(Box::new(self.diag_emitter.clone())) - } -} - fn generate_thin_lto_work( cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], needs_thin_lto: Vec<(String, B::ThinBuffer)>, @@ -381,6 +374,7 @@ fn generate_thin_lto_work( let (lto_modules, copy_jobs) = B::run_thin_lto( cgcx, + dcx, exported_symbols_for_lto, each_linked_rlib_for_lto, needs_thin_lto, @@ -825,14 +819,12 @@ pub(crate) fn compute_per_cgu_lto_type( fn execute_optimize_work_item( cgcx: &CodegenContext, + shared_emitter: SharedEmitter, mut module: ModuleCodegen, ) -> WorkItemResult { let _timer = cgcx.prof.generic_activity_with_arg("codegen_module_optimize", &*module.name); - let dcx = cgcx.create_dcx(); - let dcx = dcx.handle(); - - B::optimize(cgcx, dcx, &mut module, &cgcx.module_config); + B::optimize(cgcx, &shared_emitter, &mut module, &cgcx.module_config); // After we've done the initial round of optimizations we need to // decide whether to synchronously codegen this module or ship it @@ -853,7 +845,7 @@ fn execute_optimize_work_item( match lto_type { ComputedLtoType::No => { - let module = B::codegen(cgcx, module, &cgcx.module_config); + let module = B::codegen(cgcx, &shared_emitter, module, &cgcx.module_config); WorkItemResult::Finished(module) } ComputedLtoType::Thin => { @@ -883,12 +875,16 @@ fn execute_optimize_work_item( fn execute_copy_from_cache_work_item( cgcx: &CodegenContext, + shared_emitter: SharedEmitter, module: CachedModuleCodegen, ) -> CompiledModule { let _timer = cgcx .prof .generic_activity_with_arg("codegen_copy_artifacts_from_incr_cache", &*module.name); + let dcx = DiagCtxt::new(Box::new(shared_emitter)); + let dcx = dcx.handle(); + let incr_comp_session_dir = cgcx.incr_comp_session_dir.as_ref().unwrap(); let mut links_from_incr_cache = Vec::new(); @@ -907,11 +903,7 @@ fn execute_copy_from_cache_work_item( Some(output_path) } Err(error) => { - cgcx.create_dcx().handle().emit_err(errors::CopyPathBuf { - source_file, - output_path, - error, - }); + dcx.emit_err(errors::CopyPathBuf { source_file, output_path, error }); None } } @@ -954,7 +946,7 @@ fn execute_copy_from_cache_work_item( let bytecode = load_from_incr_cache(module_config.emit_bc, OutputType::Bitcode); let object = load_from_incr_cache(should_emit_obj, OutputType::Object); if should_emit_obj && object.is_none() { - cgcx.create_dcx().handle().emit_fatal(errors::NoSavedObjectFile { cgu_name: &module.name }) + dcx.emit_fatal(errors::NoSavedObjectFile { cgu_name: &module.name }) } CompiledModule { @@ -971,6 +963,7 @@ fn execute_copy_from_cache_work_item( fn do_fat_lto( cgcx: &CodegenContext, + shared_emitter: SharedEmitter, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], mut needs_fat_lto: Vec>, @@ -978,7 +971,10 @@ fn do_fat_lto( ) -> CompiledModule { let _timer = cgcx.prof.verbose_generic_activity("LLVM_fatlto"); - check_lto_allowed(&cgcx); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); + let dcx = dcx.handle(); + + check_lto_allowed(&cgcx, dcx); for (module, wp) in import_only_modules { needs_fat_lto.push(FatLtoInput::Serialized { name: wp.cgu_name, buffer: module }) @@ -986,15 +982,17 @@ fn do_fat_lto( let module = B::run_and_optimize_fat_lto( cgcx, + &shared_emitter, exported_symbols_for_lto, each_linked_rlib_for_lto, needs_fat_lto, ); - B::codegen(cgcx, module, &cgcx.module_config) + B::codegen(cgcx, &shared_emitter, module, &cgcx.module_config) } fn do_thin_lto<'a, B: ExtraBackendMethods>( cgcx: &'a CodegenContext, + shared_emitter: SharedEmitter, exported_symbols_for_lto: Arc>, each_linked_rlib_for_lto: Vec, needs_thin_lto: Vec<(String, ::ThinBuffer)>, @@ -1005,7 +1003,10 @@ fn do_thin_lto<'a, B: ExtraBackendMethods>( ) -> Vec { let _timer = cgcx.prof.verbose_generic_activity("LLVM_thinlto"); - check_lto_allowed(&cgcx); + let dcx = DiagCtxt::new(Box::new(shared_emitter.clone())); + let dcx = dcx.handle(); + + check_lto_allowed(&cgcx, dcx); let (coordinator_send, coordinator_receive) = channel(); @@ -1030,6 +1031,7 @@ fn do_thin_lto<'a, B: ExtraBackendMethods>( // we don't worry about tokens. for (work, cost) in generate_thin_lto_work( cgcx, + dcx, &exported_symbols_for_lto, &each_linked_rlib_for_lto, needs_thin_lto, @@ -1071,7 +1073,7 @@ fn do_thin_lto<'a, B: ExtraBackendMethods>( while used_token_count < tokens.len() + 1 && let Some((item, _)) = work_items.pop() { - spawn_thin_lto_work(&cgcx, coordinator_send.clone(), item); + spawn_thin_lto_work(&cgcx, shared_emitter.clone(), coordinator_send.clone(), item); used_token_count += 1; } } else { @@ -1095,7 +1097,7 @@ fn do_thin_lto<'a, B: ExtraBackendMethods>( } Err(e) => { let msg = &format!("failed to acquire jobserver token: {e}"); - cgcx.diag_emitter.fatal(msg); + shared_emitter.fatal(msg); codegen_aborted = Some(FatalError); } }, @@ -1133,12 +1135,13 @@ fn do_thin_lto<'a, B: ExtraBackendMethods>( fn execute_thin_lto_work_item( cgcx: &CodegenContext, + shared_emitter: SharedEmitter, module: lto::ThinModule, ) -> CompiledModule { let _timer = cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", module.name()); - let module = B::optimize_thin(cgcx, module); - B::codegen(cgcx, module, &cgcx.module_config) + let module = B::optimize_thin(cgcx, &shared_emitter, module); + B::codegen(cgcx, &shared_emitter, module, &cgcx.module_config) } /// Messages sent to the coordinator. @@ -1291,7 +1294,6 @@ fn start_executing_work( remark: sess.opts.cg.remark.clone(), remark_dir, incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()), - diag_emitter: shared_emitter.clone(), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, tm_factory: backend.target_machine_factory(tcx.sess, ol, backend_features), @@ -1488,10 +1490,7 @@ fn start_executing_work( let mut llvm_start_time: Option> = None; if let Some(allocator_module) = &mut allocator_module { - let dcx = cgcx.create_dcx(); - let dcx = dcx.handle(); - - B::optimize(&cgcx, dcx, allocator_module, &allocator_config); + B::optimize(&cgcx, &shared_emitter, allocator_module, &allocator_config); } // Run the message loop while there's still anything that needs message @@ -1529,7 +1528,13 @@ fn start_executing_work( let (item, _) = work_items.pop().expect("queue empty - queue_full_enough() broken?"); main_thread_state = MainThreadState::Lending; - spawn_work(&cgcx, coordinator_send.clone(), &mut llvm_start_time, item); + spawn_work( + &cgcx, + shared_emitter.clone(), + coordinator_send.clone(), + &mut llvm_start_time, + item, + ); } } } else if codegen_state == Completed { @@ -1547,7 +1552,13 @@ fn start_executing_work( MainThreadState::Idle => { if let Some((item, _)) = work_items.pop() { main_thread_state = MainThreadState::Lending; - spawn_work(&cgcx, coordinator_send.clone(), &mut llvm_start_time, item); + spawn_work( + &cgcx, + shared_emitter.clone(), + coordinator_send.clone(), + &mut llvm_start_time, + item, + ); } else { // There is no unstarted work, so let the main thread // take over for a running worker. Otherwise the @@ -1583,7 +1594,13 @@ fn start_executing_work( while running_with_own_token < tokens.len() && let Some((item, _)) = work_items.pop() { - spawn_work(&cgcx, coordinator_send.clone(), &mut llvm_start_time, item); + spawn_work( + &cgcx, + shared_emitter.clone(), + coordinator_send.clone(), + &mut llvm_start_time, + item, + ); running_with_own_token += 1; } } @@ -1726,6 +1743,7 @@ fn start_executing_work( // This uses the implicit token let module = do_fat_lto( &cgcx, + shared_emitter.clone(), &exported_symbols_for_lto, &each_linked_rlib_file_for_lto, needs_fat_lto, @@ -1745,6 +1763,7 @@ fn start_executing_work( compiled_modules.extend(do_thin_lto( &cgcx, + shared_emitter.clone(), exported_symbols_for_lto, each_linked_rlib_file_for_lto, needs_thin_lto, @@ -1759,8 +1778,9 @@ fn start_executing_work( Ok(CompiledModules { modules: compiled_modules, - allocator_module: allocator_module - .map(|allocator_module| B::codegen(&cgcx, allocator_module, &allocator_config)), + allocator_module: allocator_module.map(|allocator_module| { + B::codegen(&cgcx, &shared_emitter, allocator_module, &allocator_config) + }), }) }) .expect("failed to spawn coordinator thread"); @@ -1829,6 +1849,7 @@ pub(crate) struct WorkerFatalError; fn spawn_work<'a, B: ExtraBackendMethods>( cgcx: &'a CodegenContext, + shared_emitter: SharedEmitter, coordinator_send: Sender>, llvm_start_time: &mut Option>, work: WorkItem, @@ -1841,10 +1862,10 @@ fn spawn_work<'a, B: ExtraBackendMethods>( B::spawn_named_thread(cgcx.time_trace, work.short_description(), move || { let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work { - WorkItem::Optimize(m) => execute_optimize_work_item(&cgcx, m), - WorkItem::CopyPostLtoArtifacts(m) => { - WorkItemResult::Finished(execute_copy_from_cache_work_item(&cgcx, m)) - } + WorkItem::Optimize(m) => execute_optimize_work_item(&cgcx, shared_emitter, m), + WorkItem::CopyPostLtoArtifacts(m) => WorkItemResult::Finished( + execute_copy_from_cache_work_item(&cgcx, shared_emitter, m), + ), })); let msg = match result { @@ -1866,6 +1887,7 @@ fn spawn_work<'a, B: ExtraBackendMethods>( fn spawn_thin_lto_work<'a, B: ExtraBackendMethods>( cgcx: &'a CodegenContext, + shared_emitter: SharedEmitter, coordinator_send: Sender, work: ThinLtoWorkItem, ) { @@ -1873,8 +1895,10 @@ fn spawn_thin_lto_work<'a, B: ExtraBackendMethods>( B::spawn_named_thread(cgcx.time_trace, work.short_description(), move || { let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work { - ThinLtoWorkItem::CopyPostLtoArtifacts(m) => execute_copy_from_cache_work_item(&cgcx, m), - ThinLtoWorkItem::ThinLto(m) => execute_thin_lto_work_item(&cgcx, m), + ThinLtoWorkItem::CopyPostLtoArtifacts(m) => { + execute_copy_from_cache_work_item(&cgcx, shared_emitter, m) + } + ThinLtoWorkItem::ThinLto(m) => execute_thin_lto_work_item(&cgcx, shared_emitter, m), })); let msg = match result { diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index 1ac1d7ef2e2e..e1d23841118c 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -4,7 +4,7 @@ use rustc_errors::DiagCtxtHandle; use rustc_middle::dep_graph::WorkProduct; use crate::back::lto::{SerializedModule, ThinModule}; -use crate::back::write::{CodegenContext, FatLtoInput, ModuleConfig}; +use crate::back::write::{CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter}; use crate::{CompiledModule, ModuleCodegen}; pub trait WriteBackendMethods: Clone + 'static { @@ -19,6 +19,7 @@ pub trait WriteBackendMethods: Clone + 'static { /// if necessary and running any further optimizations fn run_and_optimize_fat_lto( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, @@ -28,6 +29,7 @@ pub trait WriteBackendMethods: Clone + 'static { /// can simply be copied over from the incr. comp. cache. fn run_thin_lto( cgcx: &CodegenContext, + dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], modules: Vec<(String, Self::ThinBuffer)>, @@ -37,16 +39,18 @@ pub trait WriteBackendMethods: Clone + 'static { fn print_statistics(&self); fn optimize( cgcx: &CodegenContext, - dcx: DiagCtxtHandle<'_>, + shared_emitter: &SharedEmitter, module: &mut ModuleCodegen, config: &ModuleConfig, ); fn optimize_thin( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, thin: ThinModule, ) -> ModuleCodegen; fn codegen( cgcx: &CodegenContext, + shared_emitter: &SharedEmitter, module: ModuleCodegen, config: &ModuleConfig, ) -> CompiledModule; From b13bb4b2da208c2c26d000354816400243df1b4a Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:26:47 +0000 Subject: [PATCH 008/273] Move LTO to OngoingCodegen::join --- compiler/rustc_codegen_ssa/src/back/write.rs | 140 +++++++++++++++---- compiler/rustc_driver_impl/src/lib.rs | 20 +-- compiler/rustc_errors/src/lib.rs | 2 +- compiler/rustc_span/src/fatal_error.rs | 17 +++ 4 files changed, 131 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 76f76c84dbdc..ddcd8decf303 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -16,7 +16,7 @@ use rustc_errors::emitter::Emitter; use rustc_errors::translation::Translator; use rustc_errors::{ Diag, DiagArgMap, DiagCtxt, DiagCtxtHandle, DiagMessage, ErrCode, FatalError, FatalErrorMarker, - Level, MultiSpan, Style, Suggestions, + Level, MultiSpan, Style, Suggestions, catch_fatal_errors, }; use rustc_fs_util::link_or_copy; use rustc_incremental::{ @@ -403,6 +403,29 @@ struct CompiledModules { allocator_module: Option, } +enum MaybeLtoModules { + NoLto { + modules: Vec, + allocator_module: Option, + }, + FatLto { + cgcx: CodegenContext, + exported_symbols_for_lto: Arc>, + each_linked_rlib_file_for_lto: Vec, + needs_fat_lto: Vec>, + lto_import_only_modules: + Vec<(SerializedModule<::ModuleBuffer>, WorkProduct)>, + }, + ThinLto { + cgcx: CodegenContext, + exported_symbols_for_lto: Arc>, + each_linked_rlib_file_for_lto: Vec, + needs_thin_lto: Vec<(String, ::ThinBuffer)>, + lto_import_only_modules: + Vec<(SerializedModule<::ModuleBuffer>, WorkProduct)>, + }, +} + fn need_bitcode_in_object(tcx: TyCtxt<'_>) -> bool { let sess = tcx.sess; sess.opts.cg.embed_bitcode @@ -1239,7 +1262,7 @@ fn start_executing_work( allocator_config: Arc, mut allocator_module: Option>, coordinator_send: Sender>, -) -> thread::JoinHandle> { +) -> thread::JoinHandle, ()>> { let sess = tcx.sess; let mut each_linked_rlib_for_lto = Vec::new(); @@ -1740,43 +1763,43 @@ fn start_executing_work( needs_fat_lto.push(FatLtoInput::InMemory(allocator_module)); } - // This uses the implicit token - let module = do_fat_lto( - &cgcx, - shared_emitter.clone(), - &exported_symbols_for_lto, - &each_linked_rlib_file_for_lto, + return Ok(MaybeLtoModules::FatLto { + cgcx, + exported_symbols_for_lto, + each_linked_rlib_file_for_lto, needs_fat_lto, lto_import_only_modules, - ); - compiled_modules.push(module); + }); } else if !needs_thin_lto.is_empty() || !lto_import_only_modules.is_empty() { assert!(compiled_modules.is_empty()); assert!(needs_fat_lto.is_empty()); - if cgcx.lto != Lto::ThinLocal { + if cgcx.lto == Lto::ThinLocal { + compiled_modules.extend(do_thin_lto( + &cgcx, + shared_emitter.clone(), + exported_symbols_for_lto, + each_linked_rlib_file_for_lto, + needs_thin_lto, + lto_import_only_modules, + )); + } else { if let Some(allocator_module) = allocator_module.take() { let (name, thin_buffer) = B::prepare_thin(allocator_module); needs_thin_lto.push((name, thin_buffer)); } - } - compiled_modules.extend(do_thin_lto( - &cgcx, - shared_emitter.clone(), - exported_symbols_for_lto, - each_linked_rlib_file_for_lto, - needs_thin_lto, - lto_import_only_modules, - )); + return Ok(MaybeLtoModules::ThinLto { + cgcx, + exported_symbols_for_lto, + each_linked_rlib_file_for_lto, + needs_thin_lto, + lto_import_only_modules, + }); + } } - // Regardless of what order these modules completed in, report them to - // the backend in the same order every time to ensure that we're handing - // out deterministic results. - compiled_modules.sort_by(|a, b| a.name.cmp(&b.name)); - - Ok(CompiledModules { + Ok(MaybeLtoModules::NoLto { modules: compiled_modules, allocator_module: allocator_module.map(|allocator_module| { B::codegen(&cgcx, &shared_emitter, allocator_module, &allocator_config) @@ -2074,13 +2097,13 @@ impl SharedEmitterMain { pub struct Coordinator { sender: Sender>, - future: Option>>, + future: Option, ()>>>, // Only used for the Message type. phantom: PhantomData, } impl Coordinator { - fn join(mut self) -> std::thread::Result> { + fn join(mut self) -> std::thread::Result, ()>> { self.future.take().unwrap().join() } } @@ -2111,8 +2134,9 @@ pub struct OngoingCodegen { impl OngoingCodegen { pub fn join(self, sess: &Session) -> (CodegenResults, FxIndexMap) { self.shared_emitter_main.check(sess, true); - let compiled_modules = sess.time("join_worker_thread", || match self.coordinator.join() { - Ok(Ok(compiled_modules)) => compiled_modules, + + let maybe_lto_modules = sess.time("join_worker_thread", || match self.coordinator.join() { + Ok(Ok(maybe_lto_modules)) => maybe_lto_modules, Ok(Err(())) => { sess.dcx().abort_if_errors(); panic!("expected abort due to worker thread errors") @@ -2124,6 +2148,62 @@ impl OngoingCodegen { sess.dcx().abort_if_errors(); + let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); + + // Catch fatal errors to ensure shared_emitter_main.check() can emit the actual diagnostics + let compiled_modules = catch_fatal_errors(|| match maybe_lto_modules { + MaybeLtoModules::NoLto { modules, allocator_module } => { + drop(shared_emitter); + CompiledModules { modules, allocator_module } + } + MaybeLtoModules::FatLto { + cgcx, + exported_symbols_for_lto, + each_linked_rlib_file_for_lto, + needs_fat_lto, + lto_import_only_modules, + } => CompiledModules { + modules: vec![do_fat_lto( + &cgcx, + shared_emitter, + &exported_symbols_for_lto, + &each_linked_rlib_file_for_lto, + needs_fat_lto, + lto_import_only_modules, + )], + allocator_module: None, + }, + MaybeLtoModules::ThinLto { + cgcx, + exported_symbols_for_lto, + each_linked_rlib_file_for_lto, + needs_thin_lto, + lto_import_only_modules, + } => CompiledModules { + modules: do_thin_lto( + &cgcx, + shared_emitter, + exported_symbols_for_lto, + each_linked_rlib_file_for_lto, + needs_thin_lto, + lto_import_only_modules, + ), + allocator_module: None, + }, + }); + + shared_emitter_main.check(sess, true); + + sess.dcx().abort_if_errors(); + + let mut compiled_modules = + compiled_modules.expect("fatal error emitted but not sent to SharedEmitter"); + + // Regardless of what order these modules completed in, report them to + // the backend in the same order every time to ensure that we're handing + // out deterministic results. + compiled_modules.modules.sort_by(|a, b| a.name.cmp(&b.name)); + let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir(sess, &compiled_modules); produce_final_output_artifacts(sess, &compiled_modules, &self.output_filenames); diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 9a3d7cc506cf..cf5a5fdd234c 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -19,7 +19,7 @@ use std::ffi::OsString; use std::fmt::Write as _; use std::fs::{self, File}; use std::io::{self, IsTerminal, Read, Write}; -use std::panic::{self, PanicHookInfo, catch_unwind}; +use std::panic::{self, PanicHookInfo}; use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; use std::sync::OnceLock; @@ -33,10 +33,11 @@ use rustc_codegen_ssa::{CodegenErrors, CodegenResults}; use rustc_data_structures::profiling::{ TimePassesFormat, get_resident_set_size, print_time_passes_entry, }; +pub use rustc_errors::catch_fatal_errors; use rustc_errors::emitter::stderr_destination; use rustc_errors::registry::Registry; use rustc_errors::translation::Translator; -use rustc_errors::{ColorConfig, DiagCtxt, ErrCode, FatalError, PResult, markdown}; +use rustc_errors::{ColorConfig, DiagCtxt, ErrCode, PResult, markdown}; use rustc_feature::find_gated_cfg; // This avoids a false positive with `-Wunused_crate_dependencies`. // `rust_index` isn't used in this crate's code, but it must be named in the @@ -1306,21 +1307,6 @@ fn parse_crate_attrs<'a>(sess: &'a Session) -> PResult<'a, ast::AttrVec> { parser.parse_inner_attributes() } -/// Runs a closure and catches unwinds triggered by fatal errors. -/// -/// The compiler currently unwinds with a special sentinel value to abort -/// compilation on fatal errors. This function catches that sentinel and turns -/// the panic into a `Result` instead. -pub fn catch_fatal_errors R, R>(f: F) -> Result { - catch_unwind(panic::AssertUnwindSafe(f)).map_err(|value| { - if value.is::() { - FatalError - } else { - panic::resume_unwind(value); - } - }) -} - /// Variant of `catch_fatal_errors` for the `interface::Result` return type /// that also computes the exit code. pub fn catch_with_exit_code(f: impl FnOnce()) -> i32 { diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index bbdda155496f..ada32bf0c283 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -68,7 +68,7 @@ pub use rustc_lint_defs::{Applicability, listify, pluralize}; use rustc_lint_defs::{Lint, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; pub use rustc_span::ErrorGuaranteed; -pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker}; +pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker, catch_fatal_errors}; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, DUMMY_SP, Loc, Span}; pub use snippet::Style; diff --git a/compiler/rustc_span/src/fatal_error.rs b/compiler/rustc_span/src/fatal_error.rs index 26c5711099c6..5e2d82681a11 100644 --- a/compiler/rustc_span/src/fatal_error.rs +++ b/compiler/rustc_span/src/fatal_error.rs @@ -3,6 +3,8 @@ #[must_use] pub struct FatalError; +use std::panic; + pub use rustc_data_structures::FatalErrorMarker; // Don't implement Send on FatalError. This makes it impossible to `panic_any!(FatalError)`. @@ -22,3 +24,18 @@ impl std::fmt::Display for FatalError { } impl std::error::Error for FatalError {} + +/// Runs a closure and catches unwinds triggered by fatal errors. +/// +/// The compiler currently unwinds with a special sentinel value to abort +/// compilation on fatal errors. This function catches that sentinel and turns +/// the panic into a `Result` instead. +pub fn catch_fatal_errors R, R>(f: F) -> Result { + panic::catch_unwind(panic::AssertUnwindSafe(f)).map_err(|value| { + if value.is::() { + FatalError + } else { + panic::resume_unwind(value); + } + }) +} From 7553f464308db4d5508e81dd4d4ad92252186a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 4 Dec 2025 10:41:13 +0000 Subject: [PATCH 009/273] inline constant typeck constraint --- .../src/polonius/typeck_constraints.rs | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs index e4e52962bf7f..7d406837f5ae 100644 --- a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs @@ -45,7 +45,6 @@ pub(super) fn convert_typeck_constraints<'tcx>( { localize_statement_constraint( tcx, - body, stmt, &outlives_constraint, point, @@ -74,7 +73,6 @@ pub(super) fn convert_typeck_constraints<'tcx>( /// needed CFG `from`-`to` intra-block nodes. fn localize_statement_constraint<'tcx>( tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, stmt: &Statement<'tcx>, outlives_constraint: &OutlivesConstraint<'tcx>, current_point: PointIndex, @@ -114,28 +112,22 @@ fn localize_statement_constraint<'tcx>( }, "there should be no common regions between the LHS and RHS of an assignment" ); - - let lhs_ty = body.local_decls[lhs.local].ty; - let successor_point = current_point; - compute_constraint_direction( - tcx, - outlives_constraint, - &lhs_ty, - current_point, - successor_point, - universal_regions, - ) } _ => { - // For the other cases, we localize an outlives constraint to where it arises. - LocalizedOutlivesConstraint { - source: outlives_constraint.sup, - from: current_point, - target: outlives_constraint.sub, - to: current_point, - } + // Assignments should be the only statement that can both generate constraints that + // apply on entry (specific to the RHS place) *and* others that only apply on exit (the + // subset of RHS regions that actually flow into the LHS): i.e., where midpoints would + // be used to ensure the former happen before the latter, within the same MIR Location. } } + + // We generally localize an outlives constraint to where it arises. + LocalizedOutlivesConstraint { + source: outlives_constraint.sup, + from: current_point, + target: outlives_constraint.sub, + to: current_point, + } } /// For a given outlives constraint arising from a MIR terminator, localize the constraint with the From 38f795dd028fd02b28278c43a828907e34f82cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 4 Dec 2025 11:18:06 +0000 Subject: [PATCH 010/273] update comments --- .../rustc_borrowck/src/polonius/typeck_constraints.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs index 7d406837f5ae..cfe9376fb502 100644 --- a/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/typeck_constraints.rs @@ -142,14 +142,12 @@ fn localize_terminator_constraint<'tcx>( universal_regions: &UniversalRegions<'tcx>, ) -> LocalizedOutlivesConstraint { // FIXME: check if other terminators need the same handling as `Call`s, in particular - // Assert/Yield/Drop. A handful of tests are failing with Drop related issues, as well as some - // coroutine tests, and that may be why. + // Assert/Yield/Drop. match &terminator.kind { // FIXME: also handle diverging calls. TerminatorKind::Call { destination, target: Some(target), .. } => { - // Calls are similar to assignments, and thus follow the same pattern. If there is a - // target for the call we also relate what flows into the destination here to entry to - // that successor. + // If there is a target for the call we also relate what flows into the destination here + // to entry to that successor. let destination_ty = destination.ty(&body.local_decls, tcx); let successor_location = Location { block: *target, statement_index: 0 }; let successor_point = liveness.point_from_location(successor_location); From 624135bd79ccfc6602b013225de4a33d5701035f Mon Sep 17 00:00:00 2001 From: Jens Reidel Date: Sat, 13 Dec 2025 20:16:12 +0100 Subject: [PATCH 011/273] Promote powerpc64-unknown-linux-musl to tier 2 with host tools Signed-off-by: Jens Reidel --- .../targets/powerpc64_unknown_linux_musl.rs | 4 +- src/bootstrap/src/core/build_steps/llvm.rs | 1 + src/bootstrap/src/core/download.rs | 1 + .../Dockerfile | 2 +- .../powerpc64-linux-gnu.defconfig | 0 .../dist-powerpc64-linux-musl/Dockerfile | 39 +++++++++++++++++++ .../powerpc64-unknown-linux-musl.defconfig | 15 +++++++ src/ci/github-actions/jobs.yml | 5 ++- src/doc/rustc/src/platform-support.md | 2 +- .../powerpc64-unknown-linux-musl.md | 10 +++-- 10 files changed, 70 insertions(+), 9 deletions(-) rename src/ci/docker/host-x86_64/{dist-powerpc64-linux => dist-powerpc64-linux-gnu}/Dockerfile (89%) rename src/ci/docker/host-x86_64/{dist-powerpc64-linux => dist-powerpc64-linux-gnu}/powerpc64-linux-gnu.defconfig (100%) create mode 100644 src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile create mode 100644 src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/powerpc64-unknown-linux-musl.defconfig diff --git a/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_musl.rs index b663ddf962ea..130bcacfc8cc 100644 --- a/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/targets/powerpc64_unknown_linux_musl.rs @@ -17,8 +17,8 @@ pub(crate) fn target() -> Target { llvm_target: "powerpc64-unknown-linux-musl".into(), metadata: TargetMetadata { description: Some("64-bit PowerPC Linux with musl 1.2.5".into()), - tier: Some(3), - host_tools: Some(false), + tier: Some(2), + host_tools: Some(true), std: Some(true), }, pointer_width: 64, diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index db2a76c4a2df..8acc92451519 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -223,6 +223,7 @@ pub(crate) fn is_ci_llvm_available_for_target( ("loongarch64-unknown-linux-musl", false), ("powerpc-unknown-linux-gnu", false), ("powerpc64-unknown-linux-gnu", false), + ("powerpc64-unknown-linux-musl", false), ("powerpc64le-unknown-linux-gnu", false), ("powerpc64le-unknown-linux-musl", false), ("riscv64gc-unknown-linux-gnu", false), diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs index d950dc1a1c58..274bd56aff7e 100644 --- a/src/bootstrap/src/core/download.rs +++ b/src/bootstrap/src/core/download.rs @@ -460,6 +460,7 @@ pub(crate) fn is_download_ci_available(target_triple: &str, llvm_assertions: boo "loongarch64-unknown-linux-gnu", "powerpc-unknown-linux-gnu", "powerpc64-unknown-linux-gnu", + "powerpc64-unknown-linux-musl", "powerpc64le-unknown-linux-gnu", "powerpc64le-unknown-linux-musl", "riscv64gc-unknown-linux-gnu", diff --git a/src/ci/docker/host-x86_64/dist-powerpc64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile similarity index 89% rename from src/ci/docker/host-x86_64/dist-powerpc64-linux/Dockerfile rename to src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile index 298282a76463..046406224c34 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/Dockerfile @@ -11,7 +11,7 @@ RUN sh /scripts/rustbuild-setup.sh WORKDIR /tmp COPY scripts/crosstool-ng-build.sh /scripts/ -COPY host-x86_64/dist-powerpc64-linux/powerpc64-linux-gnu.defconfig /tmp/crosstool.defconfig +COPY host-x86_64/dist-powerpc64-linux-gnu/powerpc64-linux-gnu.defconfig /tmp/crosstool.defconfig RUN /scripts/crosstool-ng-build.sh COPY scripts/sccache.sh /scripts/ diff --git a/src/ci/docker/host-x86_64/dist-powerpc64-linux/powerpc64-linux-gnu.defconfig b/src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/powerpc64-linux-gnu.defconfig similarity index 100% rename from src/ci/docker/host-x86_64/dist-powerpc64-linux/powerpc64-linux-gnu.defconfig rename to src/ci/docker/host-x86_64/dist-powerpc64-linux-gnu/powerpc64-linux-gnu.defconfig diff --git a/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile new file mode 100644 index 000000000000..7c8a1e657ac2 --- /dev/null +++ b/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/Dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:22.04 + +COPY scripts/cross-apt-packages.sh /scripts/ +RUN sh /scripts/cross-apt-packages.sh + +COPY scripts/crosstool-ng.sh /scripts/ +RUN sh /scripts/crosstool-ng.sh + +COPY scripts/rustbuild-setup.sh /scripts/ +RUN sh /scripts/rustbuild-setup.sh + +WORKDIR /tmp + +COPY scripts/crosstool-ng-build.sh /scripts/ +COPY host-x86_64/dist-powerpc64-linux-musl/powerpc64-unknown-linux-musl.defconfig /tmp/crosstool.defconfig +RUN /scripts/crosstool-ng-build.sh + +COPY scripts/sccache.sh /scripts/ +RUN sh /scripts/sccache.sh + +ENV PATH=$PATH:/x-tools/powerpc64-unknown-linux-musl/bin + +ENV \ + AR_powerpc64_unknown_linux_musl=powerpc64-unknown-linux-musl-ar \ + CC_powerpc64_unknown_linux_musl=powerpc64-unknown-linux-musl-gcc \ + CXX_powerpc64_unknown_linux_musl=powerpc64-unknown-linux-musl-g++ + +ENV HOSTS=powerpc64-unknown-linux-musl + +ENV RUST_CONFIGURE_ARGS \ + --enable-extended \ + --enable-full-tools \ + --enable-profiler \ + --enable-sanitizers \ + --disable-docs \ + --set target.powerpc64-unknown-linux-musl.crt-static=false \ + --musl-root-powerpc64=/x-tools/powerpc64-unknown-linux-musl/powerpc64-unknown-linux-musl/sysroot/usr + +ENV SCRIPT python3 ../x.py dist --host $HOSTS --target $HOSTS diff --git a/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/powerpc64-unknown-linux-musl.defconfig b/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/powerpc64-unknown-linux-musl.defconfig new file mode 100644 index 000000000000..08132d3ab8ba --- /dev/null +++ b/src/ci/docker/host-x86_64/dist-powerpc64-linux-musl/powerpc64-unknown-linux-musl.defconfig @@ -0,0 +1,15 @@ +CT_CONFIG_VERSION="4" +CT_EXPERIMENTAL=y +CT_PREFIX_DIR="/x-tools/${CT_TARGET}" +CT_USE_MIRROR=y +CT_MIRROR_BASE_URL="https://ci-mirrors.rust-lang.org/rustc" +CT_ARCH_POWERPC=y +CT_ARCH_64=y +CT_ARCH_ABI="elfv2" +# CT_DEMULTILIB is not set +CT_KERNEL_LINUX=y +CT_LINUX_V_4_19=y +CT_LIBC_MUSL=y +CT_MUSL_V_1_2_5=y +CT_CC_LANG_CXX=y +CT_GETTEXT_NEEDED=y diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 9acad5c06b21..38c30f8b8e29 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -232,7 +232,10 @@ auto: - name: dist-powerpc-linux <<: *job-linux-4c - - name: dist-powerpc64-linux + - name: dist-powerpc64-linux-gnu + <<: *job-linux-4c + + - name: dist-powerpc64-linux-musl <<: *job-linux-4c - name: dist-powerpc64le-linux-gnu diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index d772702df76e..c2b26e56ef49 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -100,6 +100,7 @@ target | notes [`i686-pc-windows-gnu`](platform-support/windows-gnu.md) | 32-bit MinGW (Windows 10+, Windows Server 2016+, Pentium 4) [^x86_32-floats-return-ABI] [^win32-msvc-alignment] `powerpc-unknown-linux-gnu` | PowerPC Linux (kernel 3.2+, glibc 2.17) `powerpc64-unknown-linux-gnu` | PPC64 Linux (kernel 3.2+, glibc 2.17) +[`powerpc64-unknown-linux-musl`](platform-support/powerpc64-unknown-linux-musl.md) | PPC64 Linux (kernel 4.19+, musl 1.2.5) [`powerpc64le-unknown-linux-gnu`](platform-support/powerpc64le-unknown-linux-gnu.md) | PPC64LE Linux (kernel 3.10+, glibc 2.17) [`powerpc64le-unknown-linux-musl`](platform-support/powerpc64le-unknown-linux-musl.md) | PPC64LE Linux (kernel 4.19+, musl 1.2.5) [`riscv64gc-unknown-linux-gnu`](platform-support/riscv64gc-unknown-linux-gnu.md) | RISC-V Linux (kernel 4.20+, glibc 2.29) @@ -369,7 +370,6 @@ target | std | host | notes [`powerpc-wrs-vxworks-spe`](platform-support/vxworks.md) | ✓ | | [`powerpc64-ibm-aix`](platform-support/aix.md) | ? | | 64-bit AIX (7.2 and newer) [`powerpc64-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | PPC64 FreeBSD (ELFv2) -[`powerpc64-unknown-linux-musl`](platform-support/powerpc64-unknown-linux-musl.md) | ✓ | ✓ | PPC64 Linux (kernel 4.19, musl 1.2.5) [`powerpc64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | OpenBSD/powerpc64 [`powerpc64-wrs-vxworks`](platform-support/vxworks.md) | ✓ | | [`powerpc64le-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | PPC64LE FreeBSD diff --git a/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-musl.md b/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-musl.md index 7213e54d5a1a..23cb31af6a26 100644 --- a/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-musl.md +++ b/src/doc/rustc/src/platform-support/powerpc64-unknown-linux-musl.md @@ -1,10 +1,13 @@ # powerpc64-unknown-linux-musl -**Tier: 3** +**Tier: 2** Target for 64-bit big endian PowerPC Linux programs using musl libc. This target uses the ELF v2 ABI. +The baseline CPU required is a PowerPC 970, which means AltiVec is required and +the oldest IBM server CPU supported is therefore POWER6. + ## Target maintainers [@Gelbpunkt](https://github.com/Gelbpunkt) @@ -38,9 +41,8 @@ linker = "powerpc64-linux-musl-gcc" ## Building Rust programs -Rust does not yet ship pre-compiled artifacts for this target. To compile for -this target, you will first need to build Rust with the target enabled (see -"Building the target" above). +This target is distributed through `rustup`, and otherwise requires no +special configuration. ## Cross-compilation From f2283e2ef54539e51d47335fc3ab0435f6efd495 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Fri, 19 Sep 2025 23:51:46 +0800 Subject: [PATCH 012/273] fix: `map_unwrap_or` fail to cover `Result::unwrap_or` --- clippy_lints/src/methods/map_unwrap_or.rs | 208 ++++++++++++++---- .../src/methods/map_unwrap_or_else.rs | 71 ++++++ clippy_lints/src/methods/mod.rs | 6 +- .../src/methods/option_map_unwrap_or.rs | 180 --------------- clippy_utils/src/msrvs.rs | 2 +- lintcheck/src/input.rs | 3 +- tests/ui/map_unwrap_or_fixable.fixed | 16 ++ tests/ui/map_unwrap_or_fixable.rs | 16 ++ tests/ui/map_unwrap_or_fixable.stderr | 50 ++++- 9 files changed, 325 insertions(+), 227 deletions(-) create mode 100644 clippy_lints/src/methods/map_unwrap_or_else.rs delete mode 100644 clippy_lints/src/methods/option_map_unwrap_or.rs diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index 8eb26fb50747..b29be88fb520 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -1,71 +1,199 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet; -use clippy_utils::usage::mutated_variables; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_copy; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{Visitor, walk_path}; +use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath}; use rustc_lint::LateContext; -use rustc_span::symbol::sym; +use rustc_middle::hir::nested_filter; +use rustc_span::{Span, sym}; +use std::ops::ControlFlow; use super::MAP_UNWRAP_OR; -/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s -/// -/// Returns true if the lint was emitted +/// lint use of `map().unwrap_or()` for `Option`s and `Result`s +#[expect(clippy::too_many_arguments)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - map_arg: &'tcx hir::Expr<'_>, - unwrap_arg: &'tcx hir::Expr<'_>, + expr: &rustc_hir::Expr<'_>, + recv: &rustc_hir::Expr<'_>, + map_arg: &'tcx rustc_hir::Expr<'_>, + unwrap_recv: &rustc_hir::Expr<'_>, + unwrap_arg: &'tcx rustc_hir::Expr<'_>, + map_span: Span, msrv: Msrv, ) -> bool { // lint if the caller of `map()` is an `Option` or a `Result`. let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); - if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) { + if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR) { return false; } + // lint if the caller of `map()` is an `Option` if is_option || is_result { - // Don't make a suggestion that may fail to compile due to mutably borrowing - // the same variable twice. - let map_mutated_vars = mutated_variables(recv, cx); - let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx); - if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { - if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { + if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) { + // Replacing `.map().unwrap_or()` with `.map_or(, )` can sometimes lead to + // borrowck errors, see #10579 for one such instance. + // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint: + // ``` + // let x = vec![1, 2]; + // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x); + // ``` + // This compiles, but changing it to `map_or` will produce a compile error: + // ``` + // let x = vec![1, 2]; + // x.get(0..1).map_or(x, |s| s.to_vec()) + // ^ moving `x` here + // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure) + // ``` + // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!) + // before the call to `unwrap_or`. + + let mut unwrap_visitor = UnwrapVisitor { + cx, + identifiers: FxHashSet::default(), + }; + unwrap_visitor.visit_expr(unwrap_arg); + + let mut reference_visitor = ReferenceVisitor { + cx, + identifiers: unwrap_visitor.identifiers, + unwrap_or_span: unwrap_arg.span, + }; + + let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id)); + + // Visit the body, and return if we've found a reference + if reference_visitor.visit_body(body).is_break() { return false; } - } else { + } + + if !unwrap_arg.span.eq_ctxt(map_span) { return false; } + // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead + let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit) + if matches!(lit.node, rustc_ast::LitKind::Bool(false))) + && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND); + + let mut applicability = Applicability::MachineApplicable; + // get snippet for unwrap_or() + let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability); // lint message - let msg = if is_option { - "called `map().unwrap_or_else()` on an `Option` value" + // comparing the snippet from source to raw text ("None") below is safe + // because we already have checked the type. + let unwrap_snippet_none = is_option && unwrap_snippet == "None"; + let arg = if unwrap_snippet_none { + "None" + } else if suggest_is_some_and { + "false" } else { - "called `map().unwrap_or_else()` on a `Result` value" + "" }; - // get snippets for args to map() and unwrap_or_else() - let map_snippet = snippet(cx, map_arg.span, ".."); - let unwrap_snippet = snippet(cx, unwrap_arg.span, ".."); - // lint, with note if both map() and unwrap_or_else() have the same span - if map_arg.span.eq_ctxt(unwrap_arg.span) { - let var_snippet = snippet(cx, recv.span, ".."); - span_lint_and_sugg( - cx, - MAP_UNWRAP_OR, - expr.span, - msg, - "try", - format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"), - Applicability::MachineApplicable, - ); - return true; - } + let suggest = if unwrap_snippet_none { + "and_then()" + } else if suggest_is_some_and { + if is_result { + "is_ok_and()" + } else { + "is_some_and()" + } + } else { + "map_or(, )" + }; + let msg = format!( + "called `map().unwrap_or({arg})` on an `{}` value", + if is_option { "Option" } else { "Result" } + ); + + span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { + let map_arg_span = map_arg.span; + + let mut suggestion = vec![ + ( + map_span, + String::from(if unwrap_snippet_none { + "and_then" + } else if suggest_is_some_and { + if is_result { "is_ok_and" } else { "is_some_and" } + } else { + "map_or" + }), + ), + (expr.span.with_lo(unwrap_recv.span.hi()), String::new()), + ]; + + if !unwrap_snippet_none && !suggest_is_some_and { + suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, "))); + } + + diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability); + }); + + return true; } false } + +struct UnwrapVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + identifiers: FxHashSet, +} + +impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> { + type NestedFilter = nested_filter::All; + + fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) { + if let Res::Local(local_id) = path.res + && let Node::Pat(pat) = self.cx.tcx.hir_node(local_id) + && let PatKind::Binding(_, local_id, ..) = pat.kind + { + self.identifiers.insert(local_id); + } + walk_path(self, path); + } + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.cx.tcx + } +} + +struct ReferenceVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + identifiers: FxHashSet, + unwrap_or_span: Span, +} + +impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { + type NestedFilter = nested_filter::All; + type Result = ControlFlow<()>; + fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> { + // If we haven't found a reference yet, check if this references + // one of the locals that was moved in the `unwrap_or` argument. + // We are only interested in exprs that appear before the `unwrap_or` call. + if expr.span < self.unwrap_or_span + && let ExprKind::Path(ref path) = expr.kind + && let QPath::Resolved(_, path) = path + && let Res::Local(local_id) = path.res + && let Node::Pat(pat) = self.cx.tcx.hir_node(local_id) + && let PatKind::Binding(_, local_id, ..) = pat.kind + && self.identifiers.contains(&local_id) + { + return ControlFlow::Break(()); + } + rustc_hir::intravisit::walk_expr(self, expr) + } + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.cx.tcx + } +} diff --git a/clippy_lints/src/methods/map_unwrap_or_else.rs b/clippy_lints/src/methods/map_unwrap_or_else.rs new file mode 100644 index 000000000000..8eb26fb50747 --- /dev/null +++ b/clippy_lints/src/methods/map_unwrap_or_else.rs @@ -0,0 +1,71 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; +use clippy_utils::source::snippet; +use clippy_utils::usage::mutated_variables; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::MAP_UNWRAP_OR; + +/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s +/// +/// Returns true if the lint was emitted +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + recv: &'tcx hir::Expr<'_>, + map_arg: &'tcx hir::Expr<'_>, + unwrap_arg: &'tcx hir::Expr<'_>, + msrv: Msrv, +) -> bool { + // lint if the caller of `map()` is an `Option` or a `Result`. + let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); + let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); + + if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) { + return false; + } + + if is_option || is_result { + // Don't make a suggestion that may fail to compile due to mutably borrowing + // the same variable twice. + let map_mutated_vars = mutated_variables(recv, cx); + let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx); + if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { + if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { + return false; + } + } else { + return false; + } + + // lint message + let msg = if is_option { + "called `map().unwrap_or_else()` on an `Option` value" + } else { + "called `map().unwrap_or_else()` on a `Result` value" + }; + // get snippets for args to map() and unwrap_or_else() + let map_snippet = snippet(cx, map_arg.span, ".."); + let unwrap_snippet = snippet(cx, unwrap_arg.span, ".."); + // lint, with note if both map() and unwrap_or_else() have the same span + if map_arg.span.eq_ctxt(unwrap_arg.span) { + let var_snippet = snippet(cx, recv.span, ".."); + span_lint_and_sugg( + cx, + MAP_UNWRAP_OR, + expr.span, + msg, + "try", + format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"), + Applicability::MachineApplicable, + ); + return true; + } + } + + false +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 48842c8739c0..163205ea87be 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -74,6 +74,7 @@ mod map_err_ignore; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod map_unwrap_or_else; mod map_with_unused_argument_over_ranges; mod mut_mutex_lock; mod needless_as_bytes; @@ -89,7 +90,6 @@ mod open_options; mod option_as_ref_cloned; mod option_as_ref_deref; mod option_map_or_none; -mod option_map_unwrap_or; mod or_fun_call; mod or_then_unwrap; mod path_buf_push_overwrite; @@ -5607,7 +5607,7 @@ impl Methods { manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith); }, Some((sym::map, m_recv, [m_arg], span, _)) => { - option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv); + map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv); }, Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { obfuscated_if_else::check( @@ -5648,7 +5648,7 @@ impl Methods { (sym::unwrap_or_else, [u_arg]) => { match method_call(recv) { Some((sym::map, recv, [map_arg], _, _)) - if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {}, + if map_unwrap_or_else::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {}, Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { obfuscated_if_else::check( cx, diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs deleted file mode 100644 index 32a9b4fe7c58..000000000000 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ /dev/null @@ -1,180 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_copy; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::Applicability; -use rustc_hir::def::Res; -use rustc_hir::intravisit::{Visitor, walk_path}; -use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath}; -use rustc_lint::LateContext; -use rustc_middle::hir::nested_filter; -use rustc_span::{Span, sym}; -use std::ops::ControlFlow; - -use super::MAP_UNWRAP_OR; - -/// lint use of `map().unwrap_or()` for `Option`s -#[expect(clippy::too_many_arguments)] -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &rustc_hir::Expr<'_>, - recv: &rustc_hir::Expr<'_>, - map_arg: &'tcx rustc_hir::Expr<'_>, - unwrap_recv: &rustc_hir::Expr<'_>, - unwrap_arg: &'tcx rustc_hir::Expr<'_>, - map_span: Span, - msrv: Msrv, -) { - // lint if the caller of `map()` is an `Option` - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) { - if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) { - // Replacing `.map().unwrap_or()` with `.map_or(, )` can sometimes lead to - // borrowck errors, see #10579 for one such instance. - // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint: - // ``` - // let x = vec![1, 2]; - // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x); - // ``` - // This compiles, but changing it to `map_or` will produce a compile error: - // ``` - // let x = vec![1, 2]; - // x.get(0..1).map_or(x, |s| s.to_vec()) - // ^ moving `x` here - // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure) - // ``` - // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!) - // before the call to `unwrap_or`. - - let mut unwrap_visitor = UnwrapVisitor { - cx, - identifiers: FxHashSet::default(), - }; - unwrap_visitor.visit_expr(unwrap_arg); - - let mut reference_visitor = ReferenceVisitor { - cx, - identifiers: unwrap_visitor.identifiers, - unwrap_or_span: unwrap_arg.span, - }; - - let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id)); - - // Visit the body, and return if we've found a reference - if reference_visitor.visit_body(body).is_break() { - return; - } - } - - if !unwrap_arg.span.eq_ctxt(map_span) { - return; - } - - // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead - let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit) - if matches!(lit.node, rustc_ast::LitKind::Bool(false))) - && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND); - - let mut applicability = Applicability::MachineApplicable; - // get snippet for unwrap_or() - let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability); - // lint message - // comparing the snippet from source to raw text ("None") below is safe - // because we already have checked the type. - let arg = if unwrap_snippet == "None" { - "None" - } else if suggest_is_some_and { - "false" - } else { - "" - }; - let unwrap_snippet_none = unwrap_snippet == "None"; - let suggest = if unwrap_snippet_none { - "and_then()" - } else if suggest_is_some_and { - "is_some_and()" - } else { - "map_or(, )" - }; - let msg = format!("called `map().unwrap_or({arg})` on an `Option` value"); - - span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { - let map_arg_span = map_arg.span; - - let mut suggestion = vec![ - ( - map_span, - String::from(if unwrap_snippet_none { - "and_then" - } else if suggest_is_some_and { - "is_some_and" - } else { - "map_or" - }), - ), - (expr.span.with_lo(unwrap_recv.span.hi()), String::new()), - ]; - - if !unwrap_snippet_none && !suggest_is_some_and { - suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, "))); - } - - diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability); - }); - } -} - -struct UnwrapVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - identifiers: FxHashSet, -} - -impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> { - type NestedFilter = nested_filter::All; - - fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) { - if let Res::Local(local_id) = path.res - && let Node::Pat(pat) = self.cx.tcx.hir_node(local_id) - && let PatKind::Binding(_, local_id, ..) = pat.kind - { - self.identifiers.insert(local_id); - } - walk_path(self, path); - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} - -struct ReferenceVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - identifiers: FxHashSet, - unwrap_or_span: Span, -} - -impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { - type NestedFilter = nested_filter::All; - type Result = ControlFlow<()>; - fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> { - // If we haven't found a reference yet, check if this references - // one of the locals that was moved in the `unwrap_or` argument. - // We are only interested in exprs that appear before the `unwrap_or` call. - if expr.span < self.unwrap_or_span - && let ExprKind::Path(ref path) = expr.kind - && let QPath::Resolved(_, path) = path - && let Res::Local(local_id) = path.res - && let Node::Pat(pat) = self.cx.tcx.hir_node(local_id) - && let PatKind::Binding(_, local_id, ..) = pat.kind - && self.identifiers.contains(&local_id) - { - return ControlFlow::Break(()); - } - rustc_hir::intravisit::walk_expr(self, expr) - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 4a7fa3472cae..144ed3dec7d0 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -61,7 +61,7 @@ msrv_aliases! { 1,45,0 { STR_STRIP_PREFIX } 1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS } 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS } - 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } + 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR, RESULT_MAP_OR_ELSE } 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } 1,38,0 { POINTER_CAST, REM_EUCLID } 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS } diff --git a/lintcheck/src/input.rs b/lintcheck/src/input.rs index 7dda2b7b25f8..ee3fcaa0a84a 100644 --- a/lintcheck/src/input.rs +++ b/lintcheck/src/input.rs @@ -281,8 +281,7 @@ impl CrateWithSource { CrateSource::Path { path } => { fn is_cache_dir(entry: &DirEntry) -> bool { fs::read(entry.path().join("CACHEDIR.TAG")) - .map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55")) - .unwrap_or(false) + .is_ok_and(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55")) } // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file. diff --git a/tests/ui/map_unwrap_or_fixable.fixed b/tests/ui/map_unwrap_or_fixable.fixed index 90f3cf8bab04..a7a2f0693210 100644 --- a/tests/ui/map_unwrap_or_fixable.fixed +++ b/tests/ui/map_unwrap_or_fixable.fixed @@ -51,3 +51,19 @@ fn main() { option_methods(); result_methods(); } + +fn issue15714() { + let o: Option = Some(3); + let r: Result = Ok(3); + println!("{}", o.map_or(3, |y| y + 1)); + //~^ map_unwrap_or + println!("{}", o.map_or_else(|| 3, |y| y + 1)); + //~^ map_unwrap_or + println!("{}", r.map_or(3, |y| y + 1)); + //~^ map_unwrap_or + println!("{}", r.map_or_else(|()| 3, |y| y + 1)); + //~^ map_unwrap_or + + println!("{}", r.is_ok_and(|y| y == 1)); + //~^ map_unwrap_or +} diff --git a/tests/ui/map_unwrap_or_fixable.rs b/tests/ui/map_unwrap_or_fixable.rs index 1078c7a3cf34..f12f058c1c4b 100644 --- a/tests/ui/map_unwrap_or_fixable.rs +++ b/tests/ui/map_unwrap_or_fixable.rs @@ -57,3 +57,19 @@ fn main() { option_methods(); result_methods(); } + +fn issue15714() { + let o: Option = Some(3); + let r: Result = Ok(3); + println!("{}", o.map(|y| y + 1).unwrap_or(3)); + //~^ map_unwrap_or + println!("{}", o.map(|y| y + 1).unwrap_or_else(|| 3)); + //~^ map_unwrap_or + println!("{}", r.map(|y| y + 1).unwrap_or(3)); + //~^ map_unwrap_or + println!("{}", r.map(|y| y + 1).unwrap_or_else(|()| 3)); + //~^ map_unwrap_or + + println!("{}", r.map(|y| y == 1).unwrap_or(false)); + //~^ map_unwrap_or +} diff --git a/tests/ui/map_unwrap_or_fixable.stderr b/tests/ui/map_unwrap_or_fixable.stderr index 99e660f8dbd1..083a2510bdf2 100644 --- a/tests/ui/map_unwrap_or_fixable.stderr +++ b/tests/ui/map_unwrap_or_fixable.stderr @@ -19,5 +19,53 @@ LL | let _ = res.map(|x| x + 1) LL | | .unwrap_or_else(|_e| 0); | |_______________________________^ help: try: `res.map_or_else(|_e| 0, |x| x + 1)` -error: aborting due to 2 previous errors +error: called `map().unwrap_or()` on an `Option` value + --> tests/ui/map_unwrap_or_fixable.rs:64:20 + | +LL | println!("{}", o.map(|y| y + 1).unwrap_or(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(, )` instead + | +LL - println!("{}", o.map(|y| y + 1).unwrap_or(3)); +LL + println!("{}", o.map_or(3, |y| y + 1)); + | + +error: called `map().unwrap_or_else()` on an `Option` value + --> tests/ui/map_unwrap_or_fixable.rs:66:20 + | +LL | println!("{}", o.map(|y| y + 1).unwrap_or_else(|| 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `o.map_or_else(|| 3, |y| y + 1)` + +error: called `map().unwrap_or()` on an `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:68:20 + | +LL | println!("{}", r.map(|y| y + 1).unwrap_or(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(, )` instead + | +LL - println!("{}", r.map(|y| y + 1).unwrap_or(3)); +LL + println!("{}", r.map_or(3, |y| y + 1)); + | + +error: called `map().unwrap_or_else()` on a `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:70:20 + | +LL | println!("{}", r.map(|y| y + 1).unwrap_or_else(|()| 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `r.map_or_else(|()| 3, |y| y + 1)` + +error: called `map().unwrap_or(false)` on an `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:73:20 + | +LL | println!("{}", r.map(|y| y == 1).unwrap_or(false)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `is_ok_and()` instead + | +LL - println!("{}", r.map(|y| y == 1).unwrap_or(false)); +LL + println!("{}", r.is_ok_and(|y| y == 1)); + | + +error: aborting due to 7 previous errors From 9565b28598976f62305ea3fb9a877b2628bb1c14 Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 24 Sep 2025 00:04:49 +0800 Subject: [PATCH 013/273] fix: `map_unwrap_or` FN on references --- clippy_lints/src/methods/map_unwrap_or.rs | 18 +++---- .../src/methods/map_unwrap_or_else.rs | 6 +-- tests/ui/map_unwrap_or_fixable.fixed | 19 +++++++ tests/ui/map_unwrap_or_fixable.rs | 19 +++++++ tests/ui/map_unwrap_or_fixable.stderr | 52 ++++++++++++++++--- 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index b29be88fb520..f2e3cc5c0091 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -26,13 +26,13 @@ pub(super) fn check<'tcx>( unwrap_arg: &'tcx rustc_hir::Expr<'_>, map_span: Span, msrv: Msrv, -) -> bool { - // lint if the caller of `map()` is an `Option` or a `Result`. - let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); - let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); +) { + let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs(); + let is_option = recv_ty.is_diag_item(cx, sym::Option); + let is_result = recv_ty.is_diag_item(cx, sym::Result); if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR) { - return false; + return; } // lint if the caller of `map()` is an `Option` @@ -71,12 +71,12 @@ pub(super) fn check<'tcx>( // Visit the body, and return if we've found a reference if reference_visitor.visit_body(body).is_break() { - return false; + return; } } if !unwrap_arg.span.eq_ctxt(map_span) { - return false; + return; } // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead @@ -137,11 +137,7 @@ pub(super) fn check<'tcx>( diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability); }); - - return true; } - - false } struct UnwrapVisitor<'a, 'tcx> { diff --git a/clippy_lints/src/methods/map_unwrap_or_else.rs b/clippy_lints/src/methods/map_unwrap_or_else.rs index 8eb26fb50747..a2f157c0cb8a 100644 --- a/clippy_lints/src/methods/map_unwrap_or_else.rs +++ b/clippy_lints/src/methods/map_unwrap_or_else.rs @@ -21,9 +21,9 @@ pub(super) fn check<'tcx>( unwrap_arg: &'tcx hir::Expr<'_>, msrv: Msrv, ) -> bool { - // lint if the caller of `map()` is an `Option` or a `Result`. - let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option); - let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result); + let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs(); + let is_option = recv_ty.is_diag_item(cx, sym::Option); + let is_result = recv_ty.is_diag_item(cx, sym::Result); if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) { return false; diff --git a/tests/ui/map_unwrap_or_fixable.fixed b/tests/ui/map_unwrap_or_fixable.fixed index a7a2f0693210..1789c7f4d487 100644 --- a/tests/ui/map_unwrap_or_fixable.fixed +++ b/tests/ui/map_unwrap_or_fixable.fixed @@ -1,6 +1,7 @@ //@aux-build:option_helpers.rs #![warn(clippy::map_unwrap_or)] +#![allow(clippy::unnecessary_lazy_evaluations)] #[macro_use] extern crate option_helpers; @@ -67,3 +68,21 @@ fn issue15714() { println!("{}", r.is_ok_and(|y| y == 1)); //~^ map_unwrap_or } + +fn issue15713() { + let x = &Some(3); + println!("{}", x.map_or(3, |y| y + 1)); + //~^ map_unwrap_or + + let x: &Result = &Ok(3); + println!("{}", x.map_or(3, |y| y + 1)); + //~^ map_unwrap_or + + let x = &Some(3); + println!("{}", x.map_or_else(|| 3, |y| y + 1)); + //~^ map_unwrap_or + + let x: &Result = &Ok(3); + println!("{}", x.map_or_else(|_| 3, |y| y + 1)); + //~^ map_unwrap_or +} diff --git a/tests/ui/map_unwrap_or_fixable.rs b/tests/ui/map_unwrap_or_fixable.rs index f12f058c1c4b..309067edce4d 100644 --- a/tests/ui/map_unwrap_or_fixable.rs +++ b/tests/ui/map_unwrap_or_fixable.rs @@ -1,6 +1,7 @@ //@aux-build:option_helpers.rs #![warn(clippy::map_unwrap_or)] +#![allow(clippy::unnecessary_lazy_evaluations)] #[macro_use] extern crate option_helpers; @@ -73,3 +74,21 @@ fn issue15714() { println!("{}", r.map(|y| y == 1).unwrap_or(false)); //~^ map_unwrap_or } + +fn issue15713() { + let x = &Some(3); + println!("{}", x.map(|y| y + 1).unwrap_or(3)); + //~^ map_unwrap_or + + let x: &Result = &Ok(3); + println!("{}", x.map(|y| y + 1).unwrap_or(3)); + //~^ map_unwrap_or + + let x = &Some(3); + println!("{}", x.map(|y| y + 1).unwrap_or_else(|| 3)); + //~^ map_unwrap_or + + let x: &Result = &Ok(3); + println!("{}", x.map(|y| y + 1).unwrap_or_else(|_| 3)); + //~^ map_unwrap_or +} diff --git a/tests/ui/map_unwrap_or_fixable.stderr b/tests/ui/map_unwrap_or_fixable.stderr index 083a2510bdf2..696f516ee055 100644 --- a/tests/ui/map_unwrap_or_fixable.stderr +++ b/tests/ui/map_unwrap_or_fixable.stderr @@ -1,5 +1,5 @@ error: called `map().unwrap_or_else()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:16:13 + --> tests/ui/map_unwrap_or_fixable.rs:17:13 | LL | let _ = opt.map(|x| x + 1) | _____________^ @@ -11,7 +11,7 @@ LL | | .unwrap_or_else(|| 0); = help: to override `-D warnings` add `#[allow(clippy::map_unwrap_or)]` error: called `map().unwrap_or_else()` on a `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:47:13 + --> tests/ui/map_unwrap_or_fixable.rs:48:13 | LL | let _ = res.map(|x| x + 1) | _____________^ @@ -20,7 +20,7 @@ LL | | .unwrap_or_else(|_e| 0); | |_______________________________^ help: try: `res.map_or_else(|_e| 0, |x| x + 1)` error: called `map().unwrap_or()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:64:20 + --> tests/ui/map_unwrap_or_fixable.rs:65:20 | LL | println!("{}", o.map(|y| y + 1).unwrap_or(3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,13 +32,13 @@ LL + println!("{}", o.map_or(3, |y| y + 1)); | error: called `map().unwrap_or_else()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:66:20 + --> tests/ui/map_unwrap_or_fixable.rs:67:20 | LL | println!("{}", o.map(|y| y + 1).unwrap_or_else(|| 3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `o.map_or_else(|| 3, |y| y + 1)` error: called `map().unwrap_or()` on an `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:68:20 + --> tests/ui/map_unwrap_or_fixable.rs:69:20 | LL | println!("{}", r.map(|y| y + 1).unwrap_or(3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -50,13 +50,13 @@ LL + println!("{}", r.map_or(3, |y| y + 1)); | error: called `map().unwrap_or_else()` on a `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:70:20 + --> tests/ui/map_unwrap_or_fixable.rs:71:20 | LL | println!("{}", r.map(|y| y + 1).unwrap_or_else(|()| 3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `r.map_or_else(|()| 3, |y| y + 1)` error: called `map().unwrap_or(false)` on an `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:73:20 + --> tests/ui/map_unwrap_or_fixable.rs:74:20 | LL | println!("{}", r.map(|y| y == 1).unwrap_or(false)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -67,5 +67,41 @@ LL - println!("{}", r.map(|y| y == 1).unwrap_or(false)); LL + println!("{}", r.is_ok_and(|y| y == 1)); | -error: aborting due to 7 previous errors +error: called `map().unwrap_or()` on an `Option` value + --> tests/ui/map_unwrap_or_fixable.rs:80:20 + | +LL | println!("{}", x.map(|y| y + 1).unwrap_or(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(, )` instead + | +LL - println!("{}", x.map(|y| y + 1).unwrap_or(3)); +LL + println!("{}", x.map_or(3, |y| y + 1)); + | + +error: called `map().unwrap_or()` on an `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:84:20 + | +LL | println!("{}", x.map(|y| y + 1).unwrap_or(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(, )` instead + | +LL - println!("{}", x.map(|y| y + 1).unwrap_or(3)); +LL + println!("{}", x.map_or(3, |y| y + 1)); + | + +error: called `map().unwrap_or_else()` on an `Option` value + --> tests/ui/map_unwrap_or_fixable.rs:88:20 + | +LL | println!("{}", x.map(|y| y + 1).unwrap_or_else(|| 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map_or_else(|| 3, |y| y + 1)` + +error: called `map().unwrap_or_else()` on a `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:92:20 + | +LL | println!("{}", x.map(|y| y + 1).unwrap_or_else(|_| 3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map_or_else(|_| 3, |y| y + 1)` + +error: aborting due to 11 previous errors From bd0341471abbf02331061d76ca54fe801f95fb52 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 19 Dec 2025 11:20:38 +0100 Subject: [PATCH 014/273] Post `needless_continue` diagnostic on the right node --- clippy_lints/src/needless_continue.rs | 31 +++++++++++------------- tests/ui/needless_continue.rs | 35 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index 55208ae708b9..d1d0d31ed91a 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -1,9 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::higher; use clippy_utils::source::{indent_of, snippet_block, snippet_with_context}; use rustc_ast::Label; use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, LoopSource, StmtKind}; +use rustc_hir::{Block, Expr, ExprKind, HirId, LoopSource, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; use rustc_span::{ExpnKind, Span}; @@ -336,14 +336,9 @@ fn emit_warning(cx: &LateContext<'_>, data: &LintData<'_>, header: &str, typ: Li data.if_expr, ), }; - span_lint_and_help( - cx, - NEEDLESS_CONTINUE, - expr.span, - message, - None, - format!("{header}\n{snip}"), - ); + span_lint_hir_and_then(cx, NEEDLESS_CONTINUE, expr.hir_id, expr.span, message, |diag| { + diag.help(format!("{header}\n{snip}")); + }); } fn suggestion_snippet_for_continue_inside_if(cx: &LateContext<'_>, data: &LintData<'_>) -> String { @@ -424,11 +419,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &LateContext<'_>, data: &Lint fn check_last_stmt_in_expr(cx: &LateContext<'_>, inner_expr: &Expr<'_>, func: &F) where - F: Fn(Option<&Label>, Span), + F: Fn(HirId, Option<&Label>, Span), { match inner_expr.kind { ExprKind::Continue(continue_label) => { - func(continue_label.label.as_ref(), inner_expr.span); + func(inner_expr.hir_id, continue_label.label.as_ref(), inner_expr.span); }, ExprKind::If(_, then_block, else_block) if let ExprKind::Block(then_block, _) = then_block.kind => { check_last_stmt_in_block(cx, then_block, func); @@ -454,7 +449,7 @@ where fn check_last_stmt_in_block(cx: &LateContext<'_>, b: &Block<'_>, func: &F) where - F: Fn(Option<&Label>, Span), + F: Fn(HirId, Option<&Label>, Span), { if let Some(expr) = b.expr { check_last_stmt_in_expr(cx, expr, func); @@ -470,15 +465,17 @@ where fn check_and_warn(cx: &LateContext<'_>, expr: &Expr<'_>) { with_loop_block(expr, |loop_block, label| { - let p = |continue_label: Option<&Label>, span: Span| { + let p = |continue_hir_id, continue_label: Option<&Label>, span: Span| { if compare_labels(label, continue_label) { - span_lint_and_help( + span_lint_hir_and_then( cx, NEEDLESS_CONTINUE, + continue_hir_id, span, MSG_REDUNDANT_CONTINUE_EXPRESSION, - None, - DROP_CONTINUE_EXPRESSION_MSG, + |diag| { + diag.help(DROP_CONTINUE_EXPRESSION_MSG); + }, ); } }; diff --git a/tests/ui/needless_continue.rs b/tests/ui/needless_continue.rs index 88b7905e7fa8..3275dddf1a0f 100644 --- a/tests/ui/needless_continue.rs +++ b/tests/ui/needless_continue.rs @@ -342,3 +342,38 @@ fn issue15548() { } } } + +fn issue16256() { + fn some_condition() -> bool { + true + } + fn another_condition() -> bool { + true + } + + for _ in 0..5 { + if some_condition() { + // ... + continue; + } + + if another_condition() { + // ... + // "this `continue` expression is redundant" is posted on + // the `continue` node. + #[expect(clippy::needless_continue)] + continue; + } + } + + for _ in 0..5 { + // "This `else` block is redundant" is posted on the + // `else` node. + #[expect(clippy::needless_continue)] + if some_condition() { + // ... + } else { + continue; + } + } +} From 9bfc9a4778d41831157e2af5063e38542960ece0 Mon Sep 17 00:00:00 2001 From: WaterWhisperer Date: Thu, 18 Dec 2025 23:29:54 +0800 Subject: [PATCH 015/273] fix: `main_recursion` enable lint in no_std crates and fix broken tests --- tests/ui/crate_level_checks/entrypoint_recursion.rs | 7 +++---- .../crate_level_checks/entrypoint_recursion.stderr | 12 ++++++++++++ .../ui/crate_level_checks/no_std_main_recursion.rs | 13 +++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 tests/ui/crate_level_checks/entrypoint_recursion.stderr create mode 100644 tests/ui/crate_level_checks/no_std_main_recursion.rs diff --git a/tests/ui/crate_level_checks/entrypoint_recursion.rs b/tests/ui/crate_level_checks/entrypoint_recursion.rs index 3ded902e36b1..84147d8e9c16 100644 --- a/tests/ui/crate_level_checks/entrypoint_recursion.rs +++ b/tests/ui/crate_level_checks/entrypoint_recursion.rs @@ -1,12 +1,11 @@ -//@check-pass //@ignore-target: apple - #![feature(rustc_attrs)] - #[warn(clippy::main_recursion)] #[allow(unconditional_recursion)] #[rustc_main] fn a() { - println!("Hello, World!"); a(); + //~^ main_recursion } + +fn main() {} diff --git a/tests/ui/crate_level_checks/entrypoint_recursion.stderr b/tests/ui/crate_level_checks/entrypoint_recursion.stderr new file mode 100644 index 000000000000..d9f50d2dfc4b --- /dev/null +++ b/tests/ui/crate_level_checks/entrypoint_recursion.stderr @@ -0,0 +1,12 @@ +error: recursing into entrypoint `a` + --> tests/ui/crate_level_checks/entrypoint_recursion.rs:7:5 + | +LL | a(); + | ^ + | + = help: consider using another function for this recursion + = note: `-D clippy::main-recursion` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::main_recursion)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/crate_level_checks/no_std_main_recursion.rs b/tests/ui/crate_level_checks/no_std_main_recursion.rs new file mode 100644 index 000000000000..74763d67dd78 --- /dev/null +++ b/tests/ui/crate_level_checks/no_std_main_recursion.rs @@ -0,0 +1,13 @@ +//@check-pass +//@compile-flags: -Cpanic=abort +#![no_std] +#[warn(clippy::main_recursion)] +#[allow(unconditional_recursion)] +fn main() { + main(); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} From aede29f9ed0b85b305c0bfe6064f7e9126982145 Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Sun, 28 Dec 2025 21:23:33 +0000 Subject: [PATCH 016/273] Enhance `needless_collect` to cover vec `push` --- clippy_lints/src/methods/needless_collect.rs | 113 ++++++++++++++++--- clippy_utils/src/sym.rs | 1 + tests/ui/needless_collect.fixed | 45 ++++++++ tests/ui/needless_collect.rs | 45 ++++++++ tests/ui/needless_collect.stderr | 54 ++++++++- 5 files changed, 242 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 0e2012319147..312133689900 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -16,8 +16,8 @@ use rustc_hir::{ use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty}; -use rustc_span::Span; use rustc_span::symbol::Ident; +use rustc_span::{Span, Symbol}; const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; @@ -104,16 +104,19 @@ pub(super) fn check<'tcx>( Node::LetStmt(l) => { if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind && let ty = cx.typeck_results().expr_ty(collect_expr) - && matches!( - ty.opt_diag_name(cx), - Some(sym::Vec | sym::VecDeque | sym::BinaryHeap | sym::LinkedList) - ) + && let Some(extra_spec) = ty.opt_diag_name(cx).and_then(ExtraFunctionSpec::new) && let iter_ty = cx.typeck_results().expr_ty(iter_expr) && let Some(block) = get_enclosing_block(cx, l.hir_id) - && let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty)) + && let Some((iter_calls, extra_calls)) = + detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty), extra_spec) && let [iter_call] = &*iter_calls { - let mut used_count_visitor = UsedCountVisitor { cx, id, count: 0 }; + let mut used_count_visitor = UsedCountVisitor { + cx, + id, + extra_spec, + count: 0, + }; walk_block(&mut used_count_visitor, block); if used_count_visitor.count > 1 { return; @@ -135,11 +138,18 @@ pub(super) fn check<'tcx>( span, NEEDLESS_COLLECT_MSG, |diag| { - let iter_replacement = - format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx)); + let iter_snippet = Sugg::hir(cx, iter_expr, ".."); + let mut iter_replacement = iter_snippet.to_string(); + iter_replacement.extend(extra_calls.iter().map(|extra| extra.get_iter_method(cx))); + iter_replacement.push_str(&iter_call.get_iter_method(cx)); + + let mut remove_suggestions = vec![(l.span, String::new())]; + remove_suggestions.extend(extra_calls.iter().map(|extra| (extra.span, String::new()))); + remove_suggestions.push((iter_call.span, iter_replacement)); + diag.multipart_suggestion( iter_call.get_suggestion_text(), - vec![(l.span, String::new()), (iter_call.span, iter_replacement)], + remove_suggestions, Applicability::MaybeIncorrect, ); }, @@ -272,6 +282,7 @@ struct IterFunction { func: IterFunctionKind, span: Span, } + impl IterFunction { fn get_iter_method(&self, cx: &LateContext<'_>) -> String { match &self.func { @@ -288,6 +299,7 @@ impl IterFunction { }, } } + fn get_suggestion_text(&self) -> &'static str { match &self.func { IterFunctionKind::IntoIter(_) => { @@ -305,6 +317,7 @@ impl IterFunction { } } } + enum IterFunctionKind { IntoIter(HirId), Len, @@ -312,16 +325,59 @@ enum IterFunctionKind { Contains(Span), } +struct ExtraFunction { + kind: ExtraFunctionKind, + span: Span, +} + +impl ExtraFunction { + fn get_iter_method(&self, cx: &LateContext<'_>) -> String { + match &self.kind { + ExtraFunctionKind::Push(span) => { + let s = snippet(cx, *span, ".."); + format!(".chain([{s}])") + }, + } + } +} + +enum ExtraFunctionKind { + Push(Span), +} + +#[derive(Clone, Copy)] +struct ExtraFunctionSpec { + push_symbol: Option, +} + +impl ExtraFunctionSpec { + fn new(target: Symbol) -> Option { + match target { + sym::Vec => Some(ExtraFunctionSpec { + push_symbol: Some(sym::push), + }), + sym::VecDeque | sym::LinkedList => Some(ExtraFunctionSpec { + push_symbol: Some(sym::push_back), + }), + sym::BinaryHeap => Some(ExtraFunctionSpec { push_symbol: None }), + _ => None, + } + } +} + struct IterFunctionVisitor<'a, 'tcx> { illegal_mutable_capture_ids: HirIdSet, current_mutably_captured_ids: HirIdSet, cx: &'a LateContext<'tcx>, uses: Vec>, + extras: Vec, + extra_spec: ExtraFunctionSpec, hir_id_uses_map: FxHashMap, current_statement_hir_id: Option, seen_other: bool, target: HirId, } + impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { fn visit_block(&mut self, block: &'tcx Block<'tcx>) { for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) { @@ -384,6 +440,17 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { func: IterFunctionKind::Contains(args[0].span), span: expr.span, })), + name if Some(name) == self.extra_spec.push_symbol && self.uses.is_empty() => { + let mut span = expr.span; + // Remove the statement span if possible + if let Node::Stmt(stmt) = self.cx.tcx.parent_hir_node(expr.hir_id) { + span = stmt.span; + } + self.extras.push(ExtraFunction { + kind: ExtraFunctionKind::Push(args[0].span), + span, + }); + }, _ => { self.seen_other = true; if let Some(hir_id) = self.current_statement_hir_id { @@ -468,6 +535,7 @@ fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v> struct UsedCountVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, id: HirId, + extra_spec: ExtraFunctionSpec, count: usize, } @@ -475,11 +543,23 @@ impl<'tcx> Visitor<'tcx> for UsedCountVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if expr.res_local_id() == Some(self.id) { - self.count += 1; - } else { + if expr.res_local_id() != Some(self.id) { walk_expr(self, expr); + return; } + + let parent = self.cx.tcx.parent_hir_node(expr.hir_id); + if let Node::Expr(expr) = parent + && let ExprKind::MethodCall(method_name, _, _, _) = &expr.kind + && self + .extra_spec + .push_symbol + .is_some_and(|sym| method_name.ident.name == sym) + { + return; + } + + self.count += 1; } fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { @@ -494,12 +574,15 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>( id: HirId, cx: &'a LateContext<'tcx>, captured_ids: HirIdSet, -) -> Option> { + extra_spec: ExtraFunctionSpec, +) -> Option<(Vec, Vec)> { let mut visitor = IterFunctionVisitor { illegal_mutable_capture_ids: captured_ids, current_mutably_captured_ids: HirIdSet::default(), cx, uses: Vec::new(), + extras: Vec::new(), + extra_spec, hir_id_uses_map: FxHashMap::default(), current_statement_hir_id: None, seen_other: false, @@ -509,7 +592,7 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>( if visitor.seen_other { None } else { - Some(visitor.uses.into_iter().flatten().collect()) + Some((visitor.uses.into_iter().flatten().collect(), visitor.extras)) } } diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index a0d2e8673fe6..a36f4954a06a 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -271,6 +271,7 @@ generate! { powi, product, push, + push_back, push_str, read, read_exact, diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index 99027e79b664..fff383fb1328 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -219,3 +219,48 @@ fn issue16270() { // Do not lint, `..` implements `Index` but is not `usize` _ = &(1..3).collect::>()[..]; } + +#[warn(clippy::needless_collect)] +mod collect_push_then_iter { + use std::collections::{BinaryHeap, LinkedList}; + + fn vec_push(iter: impl Iterator) -> Vec { + + //~^ needless_collect + + iter.chain([1]).map(|x| x + 1).collect() + } + + fn vec_push_no_iter(iter: impl Iterator) { + let mut v = iter.collect::>(); + v.push(1); + } + + fn vec_push_multiple(iter: impl Iterator) -> Vec { + + //~^ needless_collect + + + iter.chain([1]).chain([2]).map(|x| x + 1).collect() + } + + fn linked_list_push(iter: impl Iterator) -> LinkedList { + + //~^ needless_collect + + iter.chain([1]).map(|x| x + 1).collect() + } + + fn binary_heap_push(iter: impl Iterator) -> BinaryHeap { + let mut v = iter.collect::>(); + v.push(1); + v.into_iter().map(|x| x + 1).collect() + } + + fn vec_push_mixed(iter: impl Iterator) -> bool { + let mut v = iter.collect::>(); + let ok = v.contains(&1); + v.push(1); + ok + } +} diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 683cc49c9af3..f6e966b09ea5 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -219,3 +219,48 @@ fn issue16270() { // Do not lint, `..` implements `Index` but is not `usize` _ = &(1..3).collect::>()[..]; } + +#[warn(clippy::needless_collect)] +mod collect_push_then_iter { + use std::collections::{BinaryHeap, LinkedList}; + + fn vec_push(iter: impl Iterator) -> Vec { + let mut v = iter.collect::>(); + //~^ needless_collect + v.push(1); + v.into_iter().map(|x| x + 1).collect() + } + + fn vec_push_no_iter(iter: impl Iterator) { + let mut v = iter.collect::>(); + v.push(1); + } + + fn vec_push_multiple(iter: impl Iterator) -> Vec { + let mut v = iter.collect::>(); + //~^ needless_collect + v.push(1); + v.push(2); + v.into_iter().map(|x| x + 1).collect() + } + + fn linked_list_push(iter: impl Iterator) -> LinkedList { + let mut v = iter.collect::>(); + //~^ needless_collect + v.push_back(1); + v.into_iter().map(|x| x + 1).collect() + } + + fn binary_heap_push(iter: impl Iterator) -> BinaryHeap { + let mut v = iter.collect::>(); + v.push(1); + v.into_iter().map(|x| x + 1).collect() + } + + fn vec_push_mixed(iter: impl Iterator) -> bool { + let mut v = iter.collect::>(); + let ok = v.contains(&1); + v.push(1); + ok + } +} diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index c77674dc55d4..93ca32a68d6a 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -121,5 +121,57 @@ error: avoid using `collect()` when not needed LL | baz((0..10), (), ('a'..='z').collect::>()) | ^^^^^^^^^^^^^^^^^^^^ help: remove this call -error: aborting due to 20 previous errors +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:228:26 + | +LL | let mut v = iter.collect::>(); + | ^^^^^^^ +... +LL | v.into_iter().map(|x| x + 1).collect() + | ------------- the iterator could be used here instead + | +help: use the original Iterator instead of collecting it and then producing a new one + | +LL ~ +LL | +LL ~ +LL ~ iter.chain([1]).map(|x| x + 1).collect() + | + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:240:26 + | +LL | let mut v = iter.collect::>(); + | ^^^^^^^ +... +LL | v.into_iter().map(|x| x + 1).collect() + | ------------- the iterator could be used here instead + | +help: use the original Iterator instead of collecting it and then producing a new one + | +LL ~ +LL | +LL ~ +LL ~ +LL ~ iter.chain([1]).chain([2]).map(|x| x + 1).collect() + | + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:248:26 + | +LL | let mut v = iter.collect::>(); + | ^^^^^^^ +... +LL | v.into_iter().map(|x| x + 1).collect() + | ------------- the iterator could be used here instead + | +help: use the original Iterator instead of collecting it and then producing a new one + | +LL ~ +LL | +LL ~ +LL ~ iter.chain([1]).map(|x| x + 1).collect() + | + +error: aborting due to 23 previous errors From 6417c8faaedca073da26585187f911946d094d7c Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Sun, 28 Dec 2025 22:26:15 +0000 Subject: [PATCH 017/273] Extend `needless_collect` to also cover `extend` --- clippy_lints/src/methods/needless_collect.rs | 89 +++++++++++++++----- tests/ui/needless_collect.fixed | 9 +- tests/ui/needless_collect.rs | 7 ++ tests/ui/needless_collect.stderr | 21 ++++- 4 files changed, 104 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 312133689900..4992cdf615fa 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::ops::ControlFlow; use super::NEEDLESS_COLLECT; @@ -144,7 +145,11 @@ pub(super) fn check<'tcx>( iter_replacement.push_str(&iter_call.get_iter_method(cx)); let mut remove_suggestions = vec![(l.span, String::new())]; - remove_suggestions.extend(extra_calls.iter().map(|extra| (extra.span, String::new()))); + remove_suggestions.extend( + extra_calls + .iter() + .flat_map(|extra| extra.span().map(|s| (s, String::new()))), + ); remove_suggestions.push((iter_call.span, iter_replacement)); diag.multipart_suggestion( @@ -325,29 +330,48 @@ enum IterFunctionKind { Contains(Span), } -struct ExtraFunction { - kind: ExtraFunctionKind, - span: Span, +struct ExtraFunctionSpan { + /// Span of the function call + func_span: Span, + /// Span of the argument + arg_span: Span, +} + +enum ExtraFunction { + Push(Vec), + Extend(ExtraFunctionSpan), } impl ExtraFunction { fn get_iter_method(&self, cx: &LateContext<'_>) -> String { - match &self.kind { - ExtraFunctionKind::Push(span) => { - let s = snippet(cx, *span, ".."); + match &self { + ExtraFunction::Push(spans) => { + let s = spans + .iter() + .map(|span| snippet(cx, span.arg_span, "..")) + .intersperse(Cow::Borrowed(", ")) + .collect::(); format!(".chain([{s}])") }, + ExtraFunction::Extend(span) => { + let s = snippet(cx, span.arg_span, ".."); + format!(".chain({s})") + }, } } -} -enum ExtraFunctionKind { - Push(Span), + fn span(&self) -> Box + '_> { + match &self { + ExtraFunction::Push(spans) => Box::new(spans.iter().map(|s| s.func_span)), + ExtraFunction::Extend(span) => Box::new(std::iter::once(span.func_span)), + } + } } #[derive(Clone, Copy)] struct ExtraFunctionSpec { push_symbol: Option, + extend_symbol: Option, } impl ExtraFunctionSpec { @@ -355,14 +379,23 @@ impl ExtraFunctionSpec { match target { sym::Vec => Some(ExtraFunctionSpec { push_symbol: Some(sym::push), + extend_symbol: Some(sym::extend), }), sym::VecDeque | sym::LinkedList => Some(ExtraFunctionSpec { push_symbol: Some(sym::push_back), + extend_symbol: Some(sym::extend), + }), + sym::BinaryHeap => Some(ExtraFunctionSpec { + push_symbol: None, + extend_symbol: None, }), - sym::BinaryHeap => Some(ExtraFunctionSpec { push_symbol: None }), _ => None, } } + + fn is_extra_function(self, name: Symbol) -> bool { + self.push_symbol == Some(name) || self.extend_symbol == Some(name) + } } struct IterFunctionVisitor<'a, 'tcx> { @@ -397,6 +430,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { } } + #[expect(clippy::too_many_lines)] fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { // Check function calls on our collection if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { @@ -446,10 +480,30 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { if let Node::Stmt(stmt) = self.cx.tcx.parent_hir_node(expr.hir_id) { span = stmt.span; } - self.extras.push(ExtraFunction { - kind: ExtraFunctionKind::Push(args[0].span), - span, - }); + if let Some(ExtraFunction::Push(spans)) = self.extras.last_mut() { + spans.push(ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + }); + } else { + self.extras.push(ExtraFunction::Push(vec![ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + }])); + } + }, + name if self.extra_spec.extend_symbol.is_some_and(|sym| name == sym) + && self.uses.is_empty() => + { + let mut span = expr.span; + // Remove the statement span if possible + if let Node::Stmt(stmt) = self.cx.tcx.parent_hir_node(expr.hir_id) { + span = stmt.span; + } + self.extras.push(ExtraFunction::Extend(ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + })); }, _ => { self.seen_other = true; @@ -551,10 +605,7 @@ impl<'tcx> Visitor<'tcx> for UsedCountVisitor<'_, 'tcx> { let parent = self.cx.tcx.parent_hir_node(expr.hir_id); if let Node::Expr(expr) = parent && let ExprKind::MethodCall(method_name, _, _, _) = &expr.kind - && self - .extra_spec - .push_symbol - .is_some_and(|sym| method_name.ident.name == sym) + && self.extra_spec.is_extra_function(method_name.ident.name) { return; } diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index fff383fb1328..e24c752870cc 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -241,7 +241,7 @@ mod collect_push_then_iter { //~^ needless_collect - iter.chain([1]).chain([2]).map(|x| x + 1).collect() + iter.chain([1, 2]).map(|x| x + 1).collect() } fn linked_list_push(iter: impl Iterator) -> LinkedList { @@ -263,4 +263,11 @@ mod collect_push_then_iter { v.push(1); ok } + + fn linked_list_extend(iter: impl Iterator, s: Vec) -> LinkedList { + + //~^ needless_collect + + iter.chain(s).map(|x| x + 1).collect() + } } diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index f6e966b09ea5..7666d5e33fdb 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -263,4 +263,11 @@ mod collect_push_then_iter { v.push(1); ok } + + fn linked_list_extend(iter: impl Iterator, s: Vec) -> LinkedList { + let mut ll = iter.collect::>(); + //~^ needless_collect + ll.extend(s); + ll.into_iter().map(|x| x + 1).collect() + } } diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 93ca32a68d6a..0decd8cd136a 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -153,7 +153,7 @@ LL ~ LL | LL ~ LL ~ -LL ~ iter.chain([1]).chain([2]).map(|x| x + 1).collect() +LL ~ iter.chain([1, 2]).map(|x| x + 1).collect() | error: avoid using `collect()` when not needed @@ -173,5 +173,22 @@ LL ~ LL ~ iter.chain([1]).map(|x| x + 1).collect() | -error: aborting due to 23 previous errors +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:268:27 + | +LL | let mut ll = iter.collect::>(); + | ^^^^^^^ +... +LL | ll.into_iter().map(|x| x + 1).collect() + | -------------- the iterator could be used here instead + | +help: use the original Iterator instead of collecting it and then producing a new one + | +LL ~ +LL | +LL ~ +LL ~ iter.chain(s).map(|x| x + 1).collect() + | + +error: aborting due to 24 previous errors From 0127720eb8587a3ffecc293020b0faa79c9b8199 Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Fri, 2 Jan 2026 01:26:15 +0000 Subject: [PATCH 018/273] Extend `needless_collect` to cover `push_front` --- clippy_lints/src/methods/needless_collect.rs | 128 ++++++++++++++----- clippy_utils/src/sym.rs | 1 + tests/ui/needless_collect.fixed | 26 +++- tests/ui/needless_collect.rs | 26 +++- tests/ui/needless_collect.stderr | 43 ++++++- 5 files changed, 187 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 4992cdf615fa..6bdb40e46b38 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -141,7 +141,9 @@ pub(super) fn check<'tcx>( |diag| { let iter_snippet = Sugg::hir(cx, iter_expr, ".."); let mut iter_replacement = iter_snippet.to_string(); - iter_replacement.extend(extra_calls.iter().map(|extra| extra.get_iter_method(cx))); + for extra in &extra_calls { + iter_replacement = extra.apply_iter_method(cx, &iter_replacement); + } iter_replacement.push_str(&iter_call.get_iter_method(cx)); let mut remove_suggestions = vec![(l.span, String::new())]; @@ -338,39 +340,62 @@ struct ExtraFunctionSpan { } enum ExtraFunction { - Push(Vec), + Push { + back: Vec, + front: Vec, + }, Extend(ExtraFunctionSpan), } impl ExtraFunction { - fn get_iter_method(&self, cx: &LateContext<'_>) -> String { + fn apply_iter_method(&self, cx: &LateContext<'_>, inner: &str) -> String { match &self { - ExtraFunction::Push(spans) => { - let s = spans + ExtraFunction::Push { back, front } => { + let back_sugg = back .iter() .map(|span| snippet(cx, span.arg_span, "..")) .intersperse(Cow::Borrowed(", ")) .collect::(); - format!(".chain([{s}])") + let front = front + .iter() + .map(|span| snippet(cx, span.arg_span, "..")) + .intersperse(Cow::Borrowed(", ")) + .collect::(); + match (front.is_empty(), back_sugg.is_empty()) { + (true, true) => inner.to_string(), + (true, false) => format!("{inner}.chain([{back_sugg}])"), + (false, true) => format!("[{front}].into_iter().chain({inner})"), + (false, false) => format!("[{front}].into_iter().chain({inner}).chain([{back_sugg}])"), + } }, ExtraFunction::Extend(span) => { let s = snippet(cx, span.arg_span, ".."); - format!(".chain({s})") + format!("{inner}.chain({s})") }, } } fn span(&self) -> Box + '_> { match &self { - ExtraFunction::Push(spans) => Box::new(spans.iter().map(|s| s.func_span)), + ExtraFunction::Push { back, front } => Box::new( + back.iter() + .map(|s| s.func_span) + .chain(front.iter().map(|s| s.func_span)), + ), ExtraFunction::Extend(span) => Box::new(std::iter::once(span.func_span)), } } } +#[derive(Clone, Copy)] +struct ExtraFunctionPushSpec { + back: Option, + front: Option, +} + #[derive(Clone, Copy)] struct ExtraFunctionSpec { - push_symbol: Option, + push_symbol: ExtraFunctionPushSpec, extend_symbol: Option, } @@ -378,15 +403,24 @@ impl ExtraFunctionSpec { fn new(target: Symbol) -> Option { match target { sym::Vec => Some(ExtraFunctionSpec { - push_symbol: Some(sym::push), + push_symbol: ExtraFunctionPushSpec { + back: Some(sym::push), + front: None, + }, extend_symbol: Some(sym::extend), }), sym::VecDeque | sym::LinkedList => Some(ExtraFunctionSpec { - push_symbol: Some(sym::push_back), + push_symbol: ExtraFunctionPushSpec { + back: Some(sym::push_back), + front: Some(sym::push_front), + }, extend_symbol: Some(sym::extend), }), sym::BinaryHeap => Some(ExtraFunctionSpec { - push_symbol: None, + push_symbol: ExtraFunctionPushSpec { + back: None, + front: None, + }, extend_symbol: None, }), _ => None, @@ -394,7 +428,7 @@ impl ExtraFunctionSpec { } fn is_extra_function(self, name: Symbol) -> bool { - self.push_symbol == Some(name) || self.extend_symbol == Some(name) + self.push_symbol.back == Some(name) || self.push_symbol.front == Some(name) || self.extend_symbol == Some(name) } } @@ -474,32 +508,48 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { func: IterFunctionKind::Contains(args[0].span), span: expr.span, })), - name if Some(name) == self.extra_spec.push_symbol && self.uses.is_empty() => { - let mut span = expr.span; - // Remove the statement span if possible - if let Node::Stmt(stmt) = self.cx.tcx.parent_hir_node(expr.hir_id) { - span = stmt.span; - } - if let Some(ExtraFunction::Push(spans)) = self.extras.last_mut() { - spans.push(ExtraFunctionSpan { - func_span: span, - arg_span: args[0].span, - }); - } else { - self.extras.push(ExtraFunction::Push(vec![ExtraFunctionSpan { - func_span: span, - arg_span: args[0].span, - }])); + name if let is_push_back = self.extra_spec.push_symbol.back.is_some_and(|sym| name == sym) + && (is_push_back || self.extra_spec.push_symbol.front.is_some_and(|sym| name == sym)) + && self.uses.is_empty() => + { + let span = get_span_of_expr_or_parent_stmt(self.cx, expr); + match self.extras.last_mut() { + Some(ExtraFunction::Push { back, .. }) if is_push_back => { + back.push(ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + }); + }, + Some(ExtraFunction::Push { front, .. }) => { + front.push(ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + }); + }, + _ if is_push_back => { + self.extras.push(ExtraFunction::Push { + back: vec![ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + }], + front: Vec::new(), + }); + }, + _ => { + self.extras.push(ExtraFunction::Push { + back: Vec::new(), + front: vec![ExtraFunctionSpan { + func_span: span, + arg_span: args[0].span, + }], + }); + }, } }, name if self.extra_spec.extend_symbol.is_some_and(|sym| name == sym) && self.uses.is_empty() => { - let mut span = expr.span; - // Remove the statement span if possible - if let Node::Stmt(stmt) = self.cx.tcx.parent_hir_node(expr.hir_id) { - span = stmt.span; - } + let span = get_span_of_expr_or_parent_stmt(self.cx, expr); self.extras.push(ExtraFunction::Extend(ExtraFunctionSpan { func_span: span, arg_span: args[0].span, @@ -542,6 +592,16 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { } } +/// If parent of the `expr` is a statement, return the span of the statement, otherwise return the +/// span of the expression. +fn get_span_of_expr_or_parent_stmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Span { + if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) { + stmt.span + } else { + expr.span + } +} + enum LoopKind<'tcx> { Conditional(&'tcx Expr<'tcx>), Loop, diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index a36f4954a06a..fbd5ce2dc2d1 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -272,6 +272,7 @@ generate! { product, push, push_back, + push_front, push_str, read, read_exact, diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index e24c752870cc..2d59c1d42b05 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -222,7 +222,7 @@ fn issue16270() { #[warn(clippy::needless_collect)] mod collect_push_then_iter { - use std::collections::{BinaryHeap, LinkedList}; + use std::collections::{BinaryHeap, LinkedList, VecDeque}; fn vec_push(iter: impl Iterator) -> Vec { @@ -270,4 +270,28 @@ mod collect_push_then_iter { iter.chain(s).map(|x| x + 1).collect() } + + fn deque_push_front(iter: impl Iterator) -> VecDeque { + + //~^ needless_collect + + + [1, 2].into_iter().chain(iter).map(|x| x + 1).collect() + } + + fn linked_list_push_front_mixed( + iter: impl Iterator, + iter2: impl Iterator, + ) -> LinkedList { + + //~^ needless_collect + + + + + + + + [5].into_iter().chain([1, 3].into_iter().chain(iter).chain([2]).chain(iter2)).chain([4, 6]).map(|x| x + 1).collect() + } } diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 7666d5e33fdb..346cddd88e70 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -222,7 +222,7 @@ fn issue16270() { #[warn(clippy::needless_collect)] mod collect_push_then_iter { - use std::collections::{BinaryHeap, LinkedList}; + use std::collections::{BinaryHeap, LinkedList, VecDeque}; fn vec_push(iter: impl Iterator) -> Vec { let mut v = iter.collect::>(); @@ -270,4 +270,28 @@ mod collect_push_then_iter { ll.extend(s); ll.into_iter().map(|x| x + 1).collect() } + + fn deque_push_front(iter: impl Iterator) -> VecDeque { + let mut v = iter.collect::>(); + //~^ needless_collect + v.push_front(1); + v.push_front(2); + v.into_iter().map(|x| x + 1).collect() + } + + fn linked_list_push_front_mixed( + iter: impl Iterator, + iter2: impl Iterator, + ) -> LinkedList { + let mut v = iter.collect::>(); + //~^ needless_collect + v.push_front(1); + v.push_back(2); + v.push_front(3); + v.extend(iter2); + v.push_back(4); + v.push_front(5); + v.push_back(6); + v.into_iter().map(|x| x + 1).collect() + } } diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 0decd8cd136a..b10312224c8e 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -190,5 +190,46 @@ LL ~ LL ~ iter.chain(s).map(|x| x + 1).collect() | -error: aborting due to 24 previous errors +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:275:26 + | +LL | let mut v = iter.collect::>(); + | ^^^^^^^ +... +LL | v.into_iter().map(|x| x + 1).collect() + | ------------- the iterator could be used here instead + | +help: use the original Iterator instead of collecting it and then producing a new one + | +LL ~ +LL | +LL ~ +LL ~ +LL ~ [1, 2].into_iter().chain(iter).map(|x| x + 1).collect() + | + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:286:26 + | +LL | let mut v = iter.collect::>(); + | ^^^^^^^ +... +LL | v.into_iter().map(|x| x + 1).collect() + | ------------- the iterator could be used here instead + | +help: use the original Iterator instead of collecting it and then producing a new one + | +LL ~ +LL | +LL ~ +LL ~ +LL ~ +LL ~ +LL ~ +LL ~ +LL ~ +LL ~ [5].into_iter().chain([1, 3].into_iter().chain(iter).chain([2]).chain(iter2)).chain([4, 6]).map(|x| x + 1).collect() + | + +error: aborting due to 26 previous errors From 65c17223c1fa4792be28f533c17f680d9eef0e66 Mon Sep 17 00:00:00 2001 From: is57primenumber <58158444+is57primenumber@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:30:19 +0900 Subject: [PATCH 019/273] add CSE optimization tests for iterating over slice --- tests/codegen-llvm/slice_cse_optimization.rs | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/codegen-llvm/slice_cse_optimization.rs diff --git a/tests/codegen-llvm/slice_cse_optimization.rs b/tests/codegen-llvm/slice_cse_optimization.rs new file mode 100644 index 000000000000..2b1851d8ae44 --- /dev/null +++ b/tests/codegen-llvm/slice_cse_optimization.rs @@ -0,0 +1,46 @@ +//! Various iterating method over slice correctly optimized using common subexpression elimination. +//! Checks function has memory(argmem: read) attribute. +//! Regression test for . +//@ compile-flags: -O + +#![crate_type = "lib"] +// CHECK-LABEL: @has_zero_iter +// CHECK-SAME: #[[ATTR:[0-9]+]] +#[inline(never)] +#[unsafe(no_mangle)] +pub fn has_zero_iter(xs: &[u8]) -> bool { + xs.iter().any(|&x| x == 0) +} + +// CHECK-LABEL: @has_zero_ptr +// CHECK-SAME: #[[ATTR]] +#[inline(never)] +#[unsafe(no_mangle)] +fn has_zero_ptr(xs: &[u8]) -> bool { + let range = xs.as_ptr_range(); + let mut start = range.start; + let end = range.end; + while start < end { + unsafe { + if *start == 0 { + return true; + } + start = start.add(1); + } + } + false +} +// CHECK-LABEL: @has_zero_for +// CHECK-SAME: #[[ATTR]] +#[inline(never)] +#[unsafe(no_mangle)] +fn has_zero_for(xs: &[u8]) -> bool { + for x in xs { + if *x == 0 { + return true; + } + } + false +} + +// CHECK: attributes #[[ATTR]] = { {{.*}}memory(argmem: read){{.*}} } From de505d24db776bf399b9932f673f3f3de8c591a0 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Mon, 5 Jan 2026 08:08:02 +0100 Subject: [PATCH 020/273] tests/debuginfo/basic-stepping.rs: Don't mix `compile-flags` with `ignore-*` We want directives nice and tidy. --- tests/debuginfo/basic-stepping.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/debuginfo/basic-stepping.rs b/tests/debuginfo/basic-stepping.rs index ffb5d87710dc..72d4e354a6f1 100644 --- a/tests/debuginfo/basic-stepping.rs +++ b/tests/debuginfo/basic-stepping.rs @@ -5,9 +5,11 @@ //@ ignore-aarch64: Doesn't work yet. //@ ignore-loongarch64: Doesn't work yet. //@ ignore-riscv64: Doesn't work yet. -//@ compile-flags: -g //@ ignore-backends: gcc +// Debugger tests need debuginfo +//@ compile-flags: -g + //@ gdb-command: run // FIXME(#97083): Should we be able to break on initialization of zero-sized types? // FIXME(#97083): Right now the first breakable line is: From 622572f6df87a8b374159a8ee95a1195a600754b Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Tue, 7 Oct 2025 06:41:09 +0200 Subject: [PATCH 021/273] tests/debuginfo/basic-stepping.rs: Add revisions `default-mir-passes`, `no-SingleUseConsts-mir-pass` To prevent regressions our test must cover the code both inside and outside of the `SingleUseConsts` MIR pass. Use revisions for that. --- tests/debuginfo/basic-stepping.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/debuginfo/basic-stepping.rs b/tests/debuginfo/basic-stepping.rs index 72d4e354a6f1..744f8f5dd654 100644 --- a/tests/debuginfo/basic-stepping.rs +++ b/tests/debuginfo/basic-stepping.rs @@ -10,6 +10,10 @@ // Debugger tests need debuginfo //@ compile-flags: -g +// FIXME(#128945): SingleUseConsts shouldn't need to be disabled. +//@ revisions: default-mir-passes no-SingleUseConsts-mir-pass +//@ [no-SingleUseConsts-mir-pass] compile-flags: -Zmir-enable-passes=-SingleUseConsts + //@ gdb-command: run // FIXME(#97083): Should we be able to break on initialization of zero-sized types? // FIXME(#97083): Right now the first breakable line is: @@ -17,12 +21,12 @@ //@ gdb-command: next //@ gdb-check: let d = c = 99; //@ gdb-command: next -// FIXME(#33013): gdb-check: let e = "hi bob"; -// FIXME(#33013): gdb-command: next -// FIXME(#33013): gdb-check: let f = b"hi bob"; -// FIXME(#33013): gdb-command: next -// FIXME(#33013): gdb-check: let g = b'9'; -// FIXME(#33013): gdb-command: next +//@ [no-SingleUseConsts-mir-pass] gdb-check: let e = "hi bob"; +//@ [no-SingleUseConsts-mir-pass] gdb-command: next +//@ [no-SingleUseConsts-mir-pass] gdb-check: let f = b"hi bob"; +//@ [no-SingleUseConsts-mir-pass] gdb-command: next +//@ [no-SingleUseConsts-mir-pass] gdb-check: let g = b'9'; +//@ [no-SingleUseConsts-mir-pass] gdb-command: next //@ gdb-check: let h = ["whatever"; 8]; //@ gdb-command: next //@ gdb-check: let i = [1,2,3,4]; From ce012da28e4d69cc24a4ecb6afb262564183f2bc Mon Sep 17 00:00:00 2001 From: andjsrk Date: Tue, 6 Jan 2026 20:14:52 +0900 Subject: [PATCH 022/273] accept test changes related to binary ops --- tests/ui/manual_clamp.fixed | 3 +- tests/ui/manual_clamp.rs | 3 +- tests/ui/manual_clamp.stderr | 70 +++++++++++++-------------- tests/ui/needless_bitwise_bool.fixed | 4 +- tests/ui/needless_bitwise_bool.rs | 2 + tests/ui/needless_bitwise_bool.stderr | 8 ++- 6 files changed, 51 insertions(+), 39 deletions(-) diff --git a/tests/ui/manual_clamp.fixed b/tests/ui/manual_clamp.fixed index 2450a4f4c611..b279a413bc17 100644 --- a/tests/ui/manual_clamp.fixed +++ b/tests/ui/manual_clamp.fixed @@ -4,7 +4,8 @@ dead_code, clippy::unnecessary_operation, clippy::no_effect, - clippy::if_same_then_else + clippy::if_same_then_else, + clippy::needless_match )] use std::cmp::{max as cmp_max, min as cmp_min}; diff --git a/tests/ui/manual_clamp.rs b/tests/ui/manual_clamp.rs index ee341d50768f..7fda41cd4c35 100644 --- a/tests/ui/manual_clamp.rs +++ b/tests/ui/manual_clamp.rs @@ -4,7 +4,8 @@ dead_code, clippy::unnecessary_operation, clippy::no_effect, - clippy::if_same_then_else + clippy::if_same_then_else, + clippy::needless_match )] use std::cmp::{max as cmp_max, min as cmp_min}; diff --git a/tests/ui/manual_clamp.stderr b/tests/ui/manual_clamp.stderr index 4a0e4fa51646..775a16418eba 100644 --- a/tests/ui/manual_clamp.stderr +++ b/tests/ui/manual_clamp.stderr @@ -1,5 +1,5 @@ error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:212:5 + --> tests/ui/manual_clamp.rs:213:5 | LL | / if x9 < CONST_MIN { LL | | @@ -15,7 +15,7 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::manual_clamp)]` error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:230:5 + --> tests/ui/manual_clamp.rs:231:5 | LL | / if x11 > CONST_MAX { LL | | @@ -29,7 +29,7 @@ LL | | } = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:240:5 + --> tests/ui/manual_clamp.rs:241:5 | LL | / if CONST_MIN > x12 { LL | | @@ -43,7 +43,7 @@ LL | | } = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:250:5 + --> tests/ui/manual_clamp.rs:251:5 | LL | / if CONST_MAX < x13 { LL | | @@ -57,7 +57,7 @@ LL | | } = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:363:5 + --> tests/ui/manual_clamp.rs:364:5 | LL | / if CONST_MAX < x35 { LL | | @@ -71,7 +71,7 @@ LL | | } = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:144:14 + --> tests/ui/manual_clamp.rs:145:14 | LL | let x0 = if CONST_MAX < input { | ______________^ @@ -86,7 +86,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:154:14 + --> tests/ui/manual_clamp.rs:155:14 | LL | let x1 = if input > CONST_MAX { | ______________^ @@ -101,7 +101,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:164:14 + --> tests/ui/manual_clamp.rs:165:14 | LL | let x2 = if input < CONST_MIN { | ______________^ @@ -116,7 +116,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:174:14 + --> tests/ui/manual_clamp.rs:175:14 | LL | let x3 = if CONST_MIN > input { | ______________^ @@ -131,7 +131,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:184:14 + --> tests/ui/manual_clamp.rs:185:14 | LL | let x4 = input.max(CONST_MIN).min(CONST_MAX); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -139,7 +139,7 @@ LL | let x4 = input.max(CONST_MIN).min(CONST_MAX); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:187:14 + --> tests/ui/manual_clamp.rs:188:14 | LL | let x5 = input.min(CONST_MAX).max(CONST_MIN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -147,7 +147,7 @@ LL | let x5 = input.min(CONST_MAX).max(CONST_MIN); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:190:14 + --> tests/ui/manual_clamp.rs:191:14 | LL | let x6 = match input { | ______________^ @@ -161,7 +161,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:197:14 + --> tests/ui/manual_clamp.rs:198:14 | LL | let x7 = match input { | ______________^ @@ -175,7 +175,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:204:14 + --> tests/ui/manual_clamp.rs:205:14 | LL | let x8 = match input { | ______________^ @@ -189,7 +189,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:221:15 + --> tests/ui/manual_clamp.rs:222:15 | LL | let x10 = match input { | _______________^ @@ -203,7 +203,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:259:15 + --> tests/ui/manual_clamp.rs:260:15 | LL | let x14 = if input > CONST_MAX { | _______________^ @@ -218,7 +218,7 @@ LL | | }; = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:270:19 + --> tests/ui/manual_clamp.rs:271:19 | LL | let x15 = if input > CONST_F64_MAX { | ___________________^ @@ -234,7 +234,7 @@ LL | | }; = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:283:19 + --> tests/ui/manual_clamp.rs:284:19 | LL | let x16 = cmp_max(cmp_min(input, CONST_MAX), CONST_MIN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -242,7 +242,7 @@ LL | let x16 = cmp_max(cmp_min(input, CONST_MAX), CONST_MIN); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:286:19 + --> tests/ui/manual_clamp.rs:287:19 | LL | let x17 = cmp_min(cmp_max(input, CONST_MIN), CONST_MAX); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -250,7 +250,7 @@ LL | let x17 = cmp_min(cmp_max(input, CONST_MIN), CONST_MAX); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:289:19 + --> tests/ui/manual_clamp.rs:290:19 | LL | let x18 = cmp_max(CONST_MIN, cmp_min(input, CONST_MAX)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -258,7 +258,7 @@ LL | let x18 = cmp_max(CONST_MIN, cmp_min(input, CONST_MAX)); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:292:19 + --> tests/ui/manual_clamp.rs:293:19 | LL | let x19 = cmp_min(CONST_MAX, cmp_max(input, CONST_MIN)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -266,7 +266,7 @@ LL | let x19 = cmp_min(CONST_MAX, cmp_max(input, CONST_MIN)); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:295:19 + --> tests/ui/manual_clamp.rs:296:19 | LL | let x20 = cmp_max(cmp_min(CONST_MAX, input), CONST_MIN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -274,7 +274,7 @@ LL | let x20 = cmp_max(cmp_min(CONST_MAX, input), CONST_MIN); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:298:19 + --> tests/ui/manual_clamp.rs:299:19 | LL | let x21 = cmp_min(cmp_max(CONST_MIN, input), CONST_MAX); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -282,7 +282,7 @@ LL | let x21 = cmp_min(cmp_max(CONST_MIN, input), CONST_MAX); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:301:19 + --> tests/ui/manual_clamp.rs:302:19 | LL | let x22 = cmp_max(CONST_MIN, cmp_min(CONST_MAX, input)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -290,7 +290,7 @@ LL | let x22 = cmp_max(CONST_MIN, cmp_min(CONST_MAX, input)); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:304:19 + --> tests/ui/manual_clamp.rs:305:19 | LL | let x23 = cmp_min(CONST_MAX, cmp_max(CONST_MIN, input)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_MIN, CONST_MAX)` @@ -298,7 +298,7 @@ LL | let x23 = cmp_min(CONST_MAX, cmp_max(CONST_MIN, input)); = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:308:19 + --> tests/ui/manual_clamp.rs:309:19 | LL | let x24 = f64::max(f64::min(input, CONST_F64_MAX), CONST_F64_MIN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -307,7 +307,7 @@ LL | let x24 = f64::max(f64::min(input, CONST_F64_MAX), CONST_F64_MIN); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:311:19 + --> tests/ui/manual_clamp.rs:312:19 | LL | let x25 = f64::min(f64::max(input, CONST_F64_MIN), CONST_F64_MAX); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -316,7 +316,7 @@ LL | let x25 = f64::min(f64::max(input, CONST_F64_MIN), CONST_F64_MAX); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:314:19 + --> tests/ui/manual_clamp.rs:315:19 | LL | let x26 = f64::max(CONST_F64_MIN, f64::min(input, CONST_F64_MAX)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -325,7 +325,7 @@ LL | let x26 = f64::max(CONST_F64_MIN, f64::min(input, CONST_F64_MAX)); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:317:19 + --> tests/ui/manual_clamp.rs:318:19 | LL | let x27 = f64::min(CONST_F64_MAX, f64::max(input, CONST_F64_MIN)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -334,7 +334,7 @@ LL | let x27 = f64::min(CONST_F64_MAX, f64::max(input, CONST_F64_MIN)); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:320:19 + --> tests/ui/manual_clamp.rs:321:19 | LL | let x28 = f64::max(f64::min(CONST_F64_MAX, input), CONST_F64_MIN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -343,7 +343,7 @@ LL | let x28 = f64::max(f64::min(CONST_F64_MAX, input), CONST_F64_MIN); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:323:19 + --> tests/ui/manual_clamp.rs:324:19 | LL | let x29 = f64::min(f64::max(CONST_F64_MIN, input), CONST_F64_MAX); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -352,7 +352,7 @@ LL | let x29 = f64::min(f64::max(CONST_F64_MIN, input), CONST_F64_MAX); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:326:19 + --> tests/ui/manual_clamp.rs:327:19 | LL | let x30 = f64::max(CONST_F64_MIN, f64::min(CONST_F64_MAX, input)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -361,7 +361,7 @@ LL | let x30 = f64::max(CONST_F64_MIN, f64::min(CONST_F64_MAX, input)); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:329:19 + --> tests/ui/manual_clamp.rs:330:19 | LL | let x31 = f64::min(CONST_F64_MAX, f64::max(CONST_F64_MIN, input)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with clamp: `input.clamp(CONST_F64_MIN, CONST_F64_MAX)` @@ -370,7 +370,7 @@ LL | let x31 = f64::min(CONST_F64_MAX, f64::max(CONST_F64_MIN, input)); = note: clamp returns NaN if the input is NaN error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:333:5 + --> tests/ui/manual_clamp.rs:334:5 | LL | / if x32 < CONST_MIN { LL | | @@ -384,7 +384,7 @@ LL | | } = note: clamp will panic if max < min error: clamp-like pattern without using clamp function - --> tests/ui/manual_clamp.rs:525:13 + --> tests/ui/manual_clamp.rs:526:13 | LL | let _ = if input > CONST_MAX { | _____________^ diff --git a/tests/ui/needless_bitwise_bool.fixed b/tests/ui/needless_bitwise_bool.fixed index 89a3c1474f25..751d3d257000 100644 --- a/tests/ui/needless_bitwise_bool.fixed +++ b/tests/ui/needless_bitwise_bool.fixed @@ -34,7 +34,9 @@ fn main() { println!("true") // This is a const method call } - if y & (0 < 1) { + // Resolved + if y && (0 < 1) { + //~^ needless_bitwise_bool println!("true") // This is a BinOp with no side effects } } diff --git a/tests/ui/needless_bitwise_bool.rs b/tests/ui/needless_bitwise_bool.rs index f5aa7a9f3d9e..5d3ff3b2079c 100644 --- a/tests/ui/needless_bitwise_bool.rs +++ b/tests/ui/needless_bitwise_bool.rs @@ -34,7 +34,9 @@ fn main() { println!("true") // This is a const method call } + // Resolved if y & (0 < 1) { + //~^ needless_bitwise_bool println!("true") // This is a BinOp with no side effects } } diff --git a/tests/ui/needless_bitwise_bool.stderr b/tests/ui/needless_bitwise_bool.stderr index 9f14646c3e5a..4f64c7136916 100644 --- a/tests/ui/needless_bitwise_bool.stderr +++ b/tests/ui/needless_bitwise_bool.stderr @@ -7,5 +7,11 @@ LL | if y & !x { = note: `-D clippy::needless-bitwise-bool` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_bitwise_bool)]` -error: aborting due to 1 previous error +error: use of bitwise operator instead of lazy operator between booleans + --> tests/ui/needless_bitwise_bool.rs:38:8 + | +LL | if y & (0 < 1) { + | ^^^^^^^^^^^ help: try: `y && (0 < 1)` + +error: aborting due to 2 previous errors From 6b11237e7dca81a57340ac4ab5ca2437b2c59a93 Mon Sep 17 00:00:00 2001 From: mu001999 Date: Tue, 6 Jan 2026 20:23:31 +0800 Subject: [PATCH 023/273] Add span field for ConstArg --- clippy_lints/src/large_stack_arrays.rs | 2 +- clippy_lints/src/utils/author.rs | 1 + clippy_utils/src/consts.rs | 2 +- clippy_utils/src/hir_utils.rs | 16 ++++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index 620e27fa67c6..261b03abba17 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -126,7 +126,7 @@ fn might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool { let ExprKind::Repeat(_, len_ct) = expr.kind else { return false; }; - !expr.span.contains(len_ct.span()) + !expr.span.contains(len_ct.span) } expr.span.from_expansion() || is_from_proc_macro(cx, expr) || repeat_expr_might_be_expanded(expr) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index f515f9987a80..455f76edc904 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -321,6 +321,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { }, ConstArgKind::Struct(..) => chain!(self, "let ConstArgKind::Struct(..) = {const_arg}.kind"), ConstArgKind::TupleCall(..) => chain!(self, "let ConstArgKind::TupleCall(..) = {const_arg}.kind"), + ConstArgKind::Tup(..) => chain!(self, "let ConstArgKind::Tup(..) = {const_arg}.kind"), ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"), ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"), } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index a44cd31dc123..46b87fd5df96 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1140,7 +1140,7 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), - ConstArgKind::Struct(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => { + ConstArgKind::Struct(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Tup(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => { None }, }, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index f1ee534c500d..57c896c97172 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -661,6 +661,10 @@ impl HirEqInterExpr<'_, '_, '_> { } fn eq_const_arg(&mut self, left: &ConstArg<'_>, right: &ConstArg<'_>) -> bool { + if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) { + return false; + } + match (&left.kind, &right.kind) { (ConstArgKind::Path(l_p), ConstArgKind::Path(r_p)) => self.eq_qpath(l_p, r_p), (ConstArgKind::Anon(l_an), ConstArgKind::Anon(r_an)) => self.eq_body(l_an.body, r_an.body), @@ -679,11 +683,18 @@ impl HirEqInterExpr<'_, '_, '_> { .zip(*args_b) .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) } + (ConstArgKind::Tup(args_a), ConstArgKind::Tup(args_b)) => { + args_a + .iter() + .zip(*args_b) + .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) + } // Use explicit match for now since ConstArg is undergoing flux. ( ConstArgKind::Path(..) | ConstArgKind::Anon(..) | ConstArgKind::TupleCall(..) + | ConstArgKind::Tup(..) | ConstArgKind::Infer(..) | ConstArgKind::Struct(..) | ConstArgKind::Error(..), @@ -1560,6 +1571,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_const_arg(arg); } }, + ConstArgKind::Tup(args) => { + for arg in *args { + self.hash_const_arg(arg); + } + }, ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {}, } } From d37d04a37c6a9cd62e4c9518ffd47352a2a2ad1e Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Sun, 4 Jan 2026 04:51:44 +0100 Subject: [PATCH 024/273] recognize safety comments inside blocks and on same line in macros allow only whitespace between the comment marker and `SAFETY:` --- .../src/undocumented_unsafe_blocks.rs | 35 ++++ .../undocumented_unsafe_blocks.default.stderr | 156 +++++++++------- ...undocumented_unsafe_blocks.disabled.stderr | 168 ++++++++++-------- .../undocumented_unsafe_blocks.rs | 67 +++++++ 4 files changed, 288 insertions(+), 138 deletions(-) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 9d27a66a9ab8..1b26b1b32b80 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -115,6 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id) && !is_unsafe_from_proc_macro(cx, block.span) && !block_has_safety_comment(cx, block.span, self.accept_comment_above_attributes) + && !block_has_inner_safety_comment(cx, block.span) && !block_parents_have_safety_comment( self.accept_comment_above_statement, self.accept_comment_above_attributes, @@ -839,6 +840,23 @@ fn text_has_safety_comment( } } } + // Check for a comment that appears after other code on the same line (e.g., `let x = // SAFETY:`) + // This handles cases in macros where the comment is on the same line as preceding code. + // We only check the first (immediate preceding) line for this pattern. + // Only whitespace is allowed between the comment marker and `SAFETY:`. + if let Some(comment_start) = [line.find("//"), line.find("/*")].into_iter().flatten().min() + && let after_marker = &line[comment_start + 2..] // skip marker + && let trimmed = after_marker.trim_start() // skip whitespace + && trimmed.get(..7).is_some_and(|s| s.eq_ignore_ascii_case("SAFETY:")) + { + let safety_offset = 2 + (after_marker.len() - trimmed.len()); + return HasSafetyComment::Yes( + start_pos + + BytePos(u32::try_from(line_start).unwrap()) + + BytePos(u32::try_from(comment_start + safety_offset).unwrap()), + false, + ); + } // No line comments; look for the start of a block comment. // This will only find them if they are at the start of a line. let (mut line_start, mut line) = (line_start, line); @@ -894,3 +912,20 @@ fn is_const_or_static(node: &Node<'_>) -> bool { }) ) } + +fn block_has_inner_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { + let source_map = cx.sess().source_map(); + if let Ok(src) = source_map.span_to_snippet(span) + && let Some(after_brace) = src + .strip_prefix("unsafe") + .and_then(|s| s.trim_start().strip_prefix('{')) + && let Some(comment) = after_brace + .trim_start() + .strip_prefix("//") + .or_else(|| after_brace.trim_start().strip_prefix("/*")) + { + comment.trim_start().to_ascii_uppercase().starts_with("SAFETY:") + } else { + false + } +} diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr index 61e5af81d827..bcc46adda8a0 100644 --- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr @@ -1,15 +1,39 @@ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:270:19 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:244:22 + | +LL | let _x = unsafe { 1 }; + | ^^^^^^^^^^^^ +... +LL | t!(); + | ---- in this macro invocation + | + = help: consider adding a safety comment on the preceding line + = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]` + = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unsafe block missing a safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:256:13 + | +LL | unsafe { 1 }; + | ^^^^^^^^^^^^ +... +LL | t!(); + | ---- in this macro invocation + | + = help: consider adding a safety comment on the preceding line + = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unsafe block missing a safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:337:19 | LL | /* Safety: */ unsafe {} | ^^^^^^^^^ | = help: consider adding a safety comment on the preceding line - = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]` error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:275:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:342:5 | LL | unsafe {} | ^^^^^^^^^ @@ -17,7 +41,7 @@ LL | unsafe {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:14 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -25,7 +49,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:29 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:29 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -33,7 +57,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:48 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:48 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -41,7 +65,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:18 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:18 | LL | let _ = (42, unsafe {}, "test", unsafe {}); | ^^^^^^^^^ @@ -49,7 +73,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:37 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:37 | LL | let _ = (42, unsafe {}, "test", unsafe {}); | ^^^^^^^^^ @@ -57,7 +81,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:293:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:14 | LL | let _ = *unsafe { &42 }; | ^^^^^^^^^^^^^^ @@ -65,7 +89,7 @@ LL | let _ = *unsafe { &42 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:299:19 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:366:19 | LL | let _ = match unsafe {} { | ^^^^^^^^^ @@ -73,7 +97,7 @@ LL | let _ = match unsafe {} { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:306:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:14 | LL | let _ = &unsafe {}; | ^^^^^^^^^ @@ -81,7 +105,7 @@ LL | let _ = &unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:311:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:378:14 | LL | let _ = [unsafe {}; 5]; | ^^^^^^^^^ @@ -89,7 +113,7 @@ LL | let _ = [unsafe {}; 5]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:316:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:383:13 | LL | let _ = unsafe {}; | ^^^^^^^^^ @@ -97,7 +121,7 @@ LL | let _ = unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:327:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:394:8 | LL | t!(unsafe {}); | ^^^^^^^^^ @@ -105,7 +129,7 @@ LL | t!(unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:334:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:401:13 | LL | unsafe {} | ^^^^^^^^^ @@ -117,7 +141,7 @@ LL | t!(); = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:343:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:410:5 | LL | unsafe {} // SAFETY: | ^^^^^^^^^ @@ -125,7 +149,7 @@ LL | unsafe {} // SAFETY: = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:349:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:416:5 | LL | unsafe { | ^^^^^^^^ @@ -133,7 +157,7 @@ LL | unsafe { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:427:5 | LL | unsafe {}; | ^^^^^^^^^ @@ -141,7 +165,7 @@ LL | unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:365:20 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:432:20 | LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -149,7 +173,7 @@ LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:440:5 | LL | unsafe impl A for () {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +181,7 @@ LL | unsafe impl A for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:381:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:448:9 | LL | unsafe impl B for (u32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -165,7 +189,7 @@ LL | unsafe impl B for (u32) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:403:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:470:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -177,7 +201,7 @@ LL | no_safety_comment!(()); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -189,7 +213,7 @@ LL | no_safety_comment!(()); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:439:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:506:5 | LL | unsafe impl T for (i32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -197,7 +221,7 @@ LL | unsafe impl T for (i32) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -209,7 +233,7 @@ LL | no_safety_comment!(u32); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:446:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:513:5 | LL | unsafe impl T for (bool) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +241,7 @@ LL | unsafe impl T for (bool) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:493:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:560:5 | LL | unsafe impl NoComment for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -225,7 +249,7 @@ LL | unsafe impl NoComment for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:498:19 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:565:19 | LL | /* SAFETY: */ unsafe impl InlineComment for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -233,7 +257,7 @@ LL | /* SAFETY: */ unsafe impl InlineComment for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:503:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:570:5 | LL | unsafe impl TrailingComment for () {} // SAFETY: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -241,13 +265,13 @@ LL | unsafe impl TrailingComment for () {} // SAFETY: = help: consider adding a safety comment on the preceding line error: constant has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:508:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:575:5 | LL | const BIG_NUMBER: i32 = 1000000; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:574:8 | LL | // SAFETY: | ^^^^^^^ @@ -255,7 +279,7 @@ LL | // SAFETY: = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:510:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:577:5 | LL | unsafe impl Interference for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -263,7 +287,7 @@ LL | unsafe impl Interference for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:518:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:585:5 | LL | unsafe impl ImplInFn for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -271,7 +295,7 @@ LL | unsafe impl ImplInFn for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:528:1 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:595:1 | LL | unsafe impl CrateRoot for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -279,7 +303,7 @@ LL | unsafe impl CrateRoot for () {} = help: consider adding a safety comment on the preceding line error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:543:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:610:5 | LL | / let _ = { LL | | @@ -289,13 +313,13 @@ LL | | }; | |______^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:609:8 | LL | // SAFETY: this is more than one level away, so it should warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:545:12 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:612:12 | LL | if unsafe { true } { | ^^^^^^^^^^^^^^^ @@ -303,7 +327,7 @@ LL | if unsafe { true } { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:549:23 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:616:23 | LL | let bar = unsafe {}; | ^^^^^^^^^ @@ -311,7 +335,7 @@ LL | let bar = unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:638:52 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:705:52 | LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 }; | ^^^^^^^^^^^^ @@ -319,7 +343,7 @@ LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:647:41 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:714:41 | LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 }; | ^^^^^^^^^^^^ @@ -327,7 +351,7 @@ LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:657:42 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:724:42 | LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 }; | ^^^^^^^^^^^^ @@ -335,7 +359,7 @@ LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:662:40 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:729:40 | LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 }; | ^^^^^^^^^^^^ @@ -343,136 +367,136 @@ LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 }; = help: consider adding a safety comment on the preceding line error: constant has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:701:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:768:5 | LL | const UNIX_EPOCH_JULIAN_DAY: i32 = | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:699:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:766:8 | LL | // SAFETY: fail ONLY if `accept-comment-above-attribute = false` | ^^^^^^^ error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:721:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:788:5 | LL | _ = bar(); | ^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:720:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:8 | LL | // SAFETY: unnecessary_safety_comment triggers here | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: module has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:741:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:808:5 | LL | mod x {} | ^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:807:8 | LL | // SAFETY: ... | ^^^^^^^ error: module has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:746:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:813:5 | LL | mod y {} | ^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:744:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:811:8 | LL | // SAFETY: ... | ^^^^^^^ error: module has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:751:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:818:5 | LL | mod z {} | ^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:750:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:817:8 | LL | // SAFETY: ... | ^^^^^^^ error: module has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:759:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:826:5 | LL | mod y {} | ^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:757:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:824:8 | LL | // SAFETY: ... | ^^^^^^^ error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:774:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:841:9 | LL | let x = 34; | ^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:772:12 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:839:12 | LL | // SAFETY: ... | ^^^^^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:848:5 | LL | unsafe fn unsafe_comment() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider changing the `safety` comment for a `# Safety` doc comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:847:8 | LL | // SAFETY: Bla | ^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:854:5 | LL | unsafe fn unsafe_block_comment() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider changing the `safety` comment for a `# Safety` doc comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:852:8 | LL | SAFETY: Bla | ^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:858:5 | LL | fn safe_comment() {} | ^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:857:8 | LL | // SAFETY: Bla | ^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:862:5 | LL | fn safe_doc_comment() {} | ^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:861:9 | LL | /// SAFETY: Bla | ^^^^^^^ -error: aborting due to 50 previous errors +error: aborting due to 52 previous errors diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr index e252cffea916..0de8ed716bed 100644 --- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr @@ -1,15 +1,39 @@ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:270:19 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:244:22 + | +LL | let _x = unsafe { 1 }; + | ^^^^^^^^^^^^ +... +LL | t!(); + | ---- in this macro invocation + | + = help: consider adding a safety comment on the preceding line + = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]` + = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unsafe block missing a safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:256:13 + | +LL | unsafe { 1 }; + | ^^^^^^^^^^^^ +... +LL | t!(); + | ---- in this macro invocation + | + = help: consider adding a safety comment on the preceding line + = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unsafe block missing a safety comment + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:337:19 | LL | /* Safety: */ unsafe {} | ^^^^^^^^^ | = help: consider adding a safety comment on the preceding line - = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]` error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:275:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:342:5 | LL | unsafe {} | ^^^^^^^^^ @@ -17,7 +41,7 @@ LL | unsafe {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:14 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -25,7 +49,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:29 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:29 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -33,7 +57,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:48 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:48 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -41,7 +65,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:18 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:18 | LL | let _ = (42, unsafe {}, "test", unsafe {}); | ^^^^^^^^^ @@ -49,7 +73,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:37 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:37 | LL | let _ = (42, unsafe {}, "test", unsafe {}); | ^^^^^^^^^ @@ -57,7 +81,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:293:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:14 | LL | let _ = *unsafe { &42 }; | ^^^^^^^^^^^^^^ @@ -65,7 +89,7 @@ LL | let _ = *unsafe { &42 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:299:19 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:366:19 | LL | let _ = match unsafe {} { | ^^^^^^^^^ @@ -73,7 +97,7 @@ LL | let _ = match unsafe {} { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:306:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:14 | LL | let _ = &unsafe {}; | ^^^^^^^^^ @@ -81,7 +105,7 @@ LL | let _ = &unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:311:14 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:378:14 | LL | let _ = [unsafe {}; 5]; | ^^^^^^^^^ @@ -89,7 +113,7 @@ LL | let _ = [unsafe {}; 5]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:316:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:383:13 | LL | let _ = unsafe {}; | ^^^^^^^^^ @@ -97,7 +121,7 @@ LL | let _ = unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:327:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:394:8 | LL | t!(unsafe {}); | ^^^^^^^^^ @@ -105,7 +129,7 @@ LL | t!(unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:334:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:401:13 | LL | unsafe {} | ^^^^^^^^^ @@ -117,7 +141,7 @@ LL | t!(); = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:343:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:410:5 | LL | unsafe {} // SAFETY: | ^^^^^^^^^ @@ -125,7 +149,7 @@ LL | unsafe {} // SAFETY: = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:349:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:416:5 | LL | unsafe { | ^^^^^^^^ @@ -133,7 +157,7 @@ LL | unsafe { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:427:5 | LL | unsafe {}; | ^^^^^^^^^ @@ -141,7 +165,7 @@ LL | unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:365:20 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:432:20 | LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -149,7 +173,7 @@ LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:440:5 | LL | unsafe impl A for () {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -157,7 +181,7 @@ LL | unsafe impl A for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:381:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:448:9 | LL | unsafe impl B for (u32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -165,7 +189,7 @@ LL | unsafe impl B for (u32) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:403:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:470:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -177,7 +201,7 @@ LL | no_safety_comment!(()); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -189,7 +213,7 @@ LL | no_safety_comment!(()); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:439:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:506:5 | LL | unsafe impl T for (i32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -197,7 +221,7 @@ LL | unsafe impl T for (i32) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -209,7 +233,7 @@ LL | no_safety_comment!(u32); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:446:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:513:5 | LL | unsafe impl T for (bool) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -217,7 +241,7 @@ LL | unsafe impl T for (bool) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:493:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:560:5 | LL | unsafe impl NoComment for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -225,7 +249,7 @@ LL | unsafe impl NoComment for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:498:19 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:565:19 | LL | /* SAFETY: */ unsafe impl InlineComment for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -233,7 +257,7 @@ LL | /* SAFETY: */ unsafe impl InlineComment for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:503:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:570:5 | LL | unsafe impl TrailingComment for () {} // SAFETY: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -241,13 +265,13 @@ LL | unsafe impl TrailingComment for () {} // SAFETY: = help: consider adding a safety comment on the preceding line error: constant has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:508:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:575:5 | LL | const BIG_NUMBER: i32 = 1000000; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:574:8 | LL | // SAFETY: | ^^^^^^^ @@ -255,7 +279,7 @@ LL | // SAFETY: = help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]` error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:510:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:577:5 | LL | unsafe impl Interference for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -263,7 +287,7 @@ LL | unsafe impl Interference for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:518:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:585:5 | LL | unsafe impl ImplInFn for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -271,7 +295,7 @@ LL | unsafe impl ImplInFn for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:528:1 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:595:1 | LL | unsafe impl CrateRoot for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -279,7 +303,7 @@ LL | unsafe impl CrateRoot for () {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:539:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:606:9 | LL | unsafe {}; | ^^^^^^^^^ @@ -287,7 +311,7 @@ LL | unsafe {}; = help: consider adding a safety comment on the preceding line error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:543:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:610:5 | LL | / let _ = { LL | | @@ -297,13 +321,13 @@ LL | | }; | |______^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:609:8 | LL | // SAFETY: this is more than one level away, so it should warn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:545:12 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:612:12 | LL | if unsafe { true } { | ^^^^^^^^^^^^^^^ @@ -311,7 +335,7 @@ LL | if unsafe { true } { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:549:23 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:616:23 | LL | let bar = unsafe {}; | ^^^^^^^^^ @@ -319,7 +343,7 @@ LL | let bar = unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:568:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:635:9 | LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -327,7 +351,7 @@ LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:573:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:640:9 | LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -335,7 +359,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:578:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:645:9 | LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -343,7 +367,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:585:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:652:5 | LL | unsafe {} | ^^^^^^^^^ @@ -351,7 +375,7 @@ LL | unsafe {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:590:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:657:5 | LL | unsafe { | ^^^^^^^^ @@ -359,7 +383,7 @@ LL | unsafe { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:598:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:665:9 | LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -367,7 +391,7 @@ LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:604:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:671:9 | LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -375,7 +399,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:611:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:678:9 | LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -383,7 +407,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:617:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:684:5 | LL | unsafe {} | ^^^^^^^^^ @@ -391,7 +415,7 @@ LL | unsafe {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:638:52 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:705:52 | LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 }; | ^^^^^^^^^^^^ @@ -399,7 +423,7 @@ LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:647:41 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:714:41 | LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 }; | ^^^^^^^^^^^^ @@ -407,7 +431,7 @@ LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:657:42 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:724:42 | LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 }; | ^^^^^^^^^^^^ @@ -415,7 +439,7 @@ LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:662:40 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:729:40 | LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 }; | ^^^^^^^^^^^^ @@ -423,7 +447,7 @@ LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:673:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:9 | LL | unsafe { here_is_another_variable_with_long_name }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -431,7 +455,7 @@ LL | unsafe { here_is_another_variable_with_long_name }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:702:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:769:9 | LL | unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian_day_just_make_this_line_longer(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -439,19 +463,19 @@ LL | unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian = help: consider adding a safety comment on the preceding line error: statement has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:721:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:788:5 | LL | _ = bar(); | ^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:720:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:8 | LL | // SAFETY: unnecessary_safety_comment triggers here | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:735:12 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:802:12 | LL | return unsafe { h() }; | ^^^^^^^^^^^^^^ @@ -459,31 +483,31 @@ LL | return unsafe { h() }; = help: consider adding a safety comment on the preceding line error: module has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:741:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:808:5 | LL | mod x {} | ^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:807:8 | LL | // SAFETY: ... | ^^^^^^^ error: module has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:751:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:818:5 | LL | mod z {} | ^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:750:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:817:8 | LL | // SAFETY: ... | ^^^^^^^ error: unsafe block missing a safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:766:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:833:9 | LL | unsafe {} | ^^^^^^^^^ @@ -491,52 +515,52 @@ LL | unsafe {} = help: consider adding a safety comment on the preceding line error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:848:5 | LL | unsafe fn unsafe_comment() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider changing the `safety` comment for a `# Safety` doc comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:847:8 | LL | // SAFETY: Bla | ^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:854:5 | LL | unsafe fn unsafe_block_comment() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider changing the `safety` comment for a `# Safety` doc comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:852:8 | LL | SAFETY: Bla | ^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:858:5 | LL | fn safe_comment() {} | ^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:8 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:857:8 | LL | // SAFETY: Bla | ^^^^^^^ error: function has unnecessary safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:862:5 | LL | fn safe_doc_comment() {} | ^^^^^^^^^^^^^^^^^^^^^^^^ | help: consider removing the safety comment - --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:9 + --> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:861:9 | LL | /// SAFETY: Bla | ^^^^^^^ -error: aborting due to 60 previous errors +error: aborting due to 62 previous errors diff --git a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs index db9e81cf10a1..8032c388ccfe 100644 --- a/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs +++ b/tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs @@ -201,6 +201,66 @@ fn comment_macro_def() { t!(); } +fn comment_macro_def_with_let() { + macro_rules! t { + () => { + let _x = + // SAFETY: here be exactly one dragon + unsafe { 1 }; + }; + } + + t!(); +} + +#[rustfmt::skip] +fn comment_macro_def_with_let_same_line() { + macro_rules! t { + () => { + let _x =// SAFETY: same line comment + unsafe { 1 }; + }; + } + + t!(); +} + +fn inner_comment_macro_def_with_let() { + macro_rules! t { + () => { + let _x = unsafe { + // SAFETY: inside the block + 1 + }; + }; + } + + t!(); +} + +fn no_comment_macro_def_with_let() { + macro_rules! t { + () => { + let _x = unsafe { 1 }; + //~^ undocumented_unsafe_blocks + }; + } + + t!(); +} + +fn prefixed_safety_comment_macro_def_with_let() { + macro_rules! t { + () => { + let _x =// not a SAFETY: comment, should lint + unsafe { 1 }; + //~^ undocumented_unsafe_blocks + }; + } + + t!(); +} + fn non_ascii_comment() { // ॐ᧻໒ SaFeTy: ௵∰ unsafe {}; @@ -263,6 +323,13 @@ fn in_closure(x: *const u32) { let _ = || unsafe { *x }; } +fn inner_block_comment_block_style(x: *const u32) { + let _ = unsafe { + /* SAFETY: block comment inside */ + *x + }; +} + // Invalid comments #[rustfmt::skip] From 93f2e80f4a885dd4eacce00e7a52aeeb41b7ce70 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Wed, 7 Jan 2026 11:25:17 -0800 Subject: [PATCH 025/273] Add -Z large-data-threshold This flag allows specifying the threshold size for placing static data in large data sections when using the medium code model on x86-64. When using -Ccode-model=medium, data smaller than this threshold uses RIP-relative addressing (32-bit offsets), while larger data uses absolute 64-bit addressing. This allows the compiler to generate more efficient code for smaller data while still supporting data larger than 2GB. This mirrors the -mlarge-data-threshold flag available in GCC and Clang. The default threshold is 65536 bytes (64KB) if not specified, matching LLVM's default behavior. --- .../src/back/owned_target_machine.rs | 2 + compiler/rustc_codegen_llvm/src/back/write.rs | 3 + compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + .../rustc_llvm/llvm-wrapper/PassWrapper.cpp | 7 +- compiler/rustc_session/src/options.rs | 3 + .../compiler-flags/large-data-threshold.md | 27 +++++++ tests/assembly-llvm/large_data_threshold.rs | 73 +++++++++++++++++++ 7 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/large-data-threshold.md create mode 100644 tests/assembly-llvm/large_data_threshold.rs diff --git a/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs b/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs index f88932d43d2a..65cf4cad24bd 100644 --- a/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs +++ b/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs @@ -39,6 +39,7 @@ impl OwnedTargetMachine { debug_info_compression: llvm::CompressionKind, use_emulated_tls: bool, use_wasm_eh: bool, + large_data_threshold: u64, ) -> Result> { // SAFETY: llvm::LLVMRustCreateTargetMachine copies pointed to data let tm_ptr = unsafe { @@ -65,6 +66,7 @@ impl OwnedTargetMachine { debug_info_compression, use_emulated_tls, use_wasm_eh, + large_data_threshold, ) }; diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index bcadb6f0de92..6c29d0470766 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -272,6 +272,8 @@ pub(crate) fn target_machine_factory( let use_wasm_eh = wants_wasm_eh(sess); + let large_data_threshold = sess.opts.unstable_opts.large_data_threshold.unwrap_or(0); + let prof = SelfProfilerRef::clone(&sess.prof); Arc::new(move |config: TargetMachineFactoryConfig| { // Self-profile timer for invoking a factory to create a target machine. @@ -313,6 +315,7 @@ pub(crate) fn target_machine_factory( debuginfo_compression, use_emulated_tls, use_wasm_eh, + large_data_threshold, ) }) } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index a90013801c8c..70fc62d08d5c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2338,6 +2338,7 @@ unsafe extern "C" { DebugInfoCompression: CompressionKind, UseEmulatedTls: bool, UseWasmEH: bool, + LargeDataThreshold: u64, ) -> *mut TargetMachine; pub(crate) fn LLVMRustAddLibraryInfo<'a>( diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index 733f5fd0df0a..97f95ac01e86 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -305,7 +305,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine( bool EmitStackSizeSection, bool RelaxELFRelocations, bool UseInitArray, const char *SplitDwarfFile, const char *OutputObjFile, LLVMRustCompressionKind DebugInfoCompression, bool UseEmulatedTls, - bool UseWasmEH) { + bool UseWasmEH, uint64_t LargeDataThreshold) { auto OptLevel = fromRust(RustOptLevel); auto RM = fromRust(RustReloc); @@ -381,6 +381,11 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine( TargetMachine *TM = TheTarget->createTargetMachine( Trip.getTriple(), CPU, Feature, Options, RM, CM, OptLevel); #endif + + if (LargeDataThreshold != 0) { + TM->setLargeDataThreshold(LargeDataThreshold); + } + return wrap(TM); } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 21fa3321a30a..0bdca21ff644 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2444,6 +2444,9 @@ options! { `=skip-entry` `=skip-exit` Multiple options can be combined with commas."), + large_data_threshold: Option = (None, parse_opt_number, [TRACKED], + "set the threshold for objects to be stored in a \"large data\" section \ + (only effective with -Ccode-model=medium, default: 65536)"), layout_seed: Option = (None, parse_opt_number, [TRACKED], "seed layout randomization"), link_directives: bool = (true, parse_bool, [TRACKED], diff --git a/src/doc/unstable-book/src/compiler-flags/large-data-threshold.md b/src/doc/unstable-book/src/compiler-flags/large-data-threshold.md new file mode 100644 index 000000000000..27c86079a6c8 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/large-data-threshold.md @@ -0,0 +1,27 @@ +# `large-data-threshold` + +----------------------- + +This flag controls the threshold for static data to be placed in large data +sections when using the `medium` code model on x86-64. + +When using `-Ccode-model=medium`, static data smaller than this threshold will +use RIP-relative addressing (32-bit offsets), while larger data will use +absolute 64-bit addressing. This allows the compiler to generate more efficient +code for smaller data while still supporting data larger than 2GB. + +The default threshold is 65536 bytes (64KB) if not specified. + +## Example + +```sh +rustc -Ccode-model=medium -Zlarge-data-threshold=1024 main.rs +``` + +This sets the threshold to 1KB, meaning only data smaller than 1024 bytes will +use RIP-relative addressing. + +## Platform Support + +This flag is only effective on x86-64 targets when using `-Ccode-model=medium`. +On other architectures or with other code models, this flag has no effect. diff --git a/tests/assembly-llvm/large_data_threshold.rs b/tests/assembly-llvm/large_data_threshold.rs new file mode 100644 index 000000000000..f3b37eb7f83d --- /dev/null +++ b/tests/assembly-llvm/large_data_threshold.rs @@ -0,0 +1,73 @@ +// Test for -Z large_data_threshold=... +// This test verifies that with the medium code model, data above the threshold +// is placed in large data sections (.ldata, .lbss, .lrodata). +//@ assembly-output: emit-asm +//@ compile-flags: -Ccode-model=medium -Zlarge-data-threshold=4 +//@ compile-flags: --target=x86_64-unknown-linux-gnu +//@ needs-llvm-components: x86 + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} + +#[lang = "drop_in_place"] +fn drop_in_place(_: *mut T) {} + +#[used] +#[no_mangle] +// U is below the threshold, should be in .data +static mut U: u16 = 123; + +#[used] +#[no_mangle] +// V is below the threshold, should be in .bss +static mut V: u16 = 0; + +#[used] +#[no_mangle] +// W is at the threshold, should be in .data +static mut W: u32 = 123; + +#[used] +#[no_mangle] +// X is at the threshold, should be in .bss +static mut X: u32 = 0; + +#[used] +#[no_mangle] +// Y is over the threshold, should be in .ldata +static mut Y: u64 = 123; + +#[used] +#[no_mangle] +// Z is over the threshold, should be in .lbss +static mut Z: u64 = 0; + +// CHECK: .section .data.U, +// CHECK-NOT: .section +// CHECK: U: +// CHECK: .section .bss.V, +// CHECK-NOT: .section +// CHECK: V: +// CHECK: .section .data.W, +// CHECK-NOT: .section +// CHECK: W: +// CHECK: .section .bss.X, +// CHECK-NOT: .section +// CHECK: X: +// CHECK: .section .ldata.Y, +// CHECK-NOT: .section +// CHECK: Y: +// CHECK: .section .lbss.Z, +// CHECK-NOT: .section +// CHECK: Z: From 0db25dbd9dfef774e4dcc2a00e37674b5bac87de Mon Sep 17 00:00:00 2001 From: yanglsh Date: Wed, 24 Sep 2025 08:53:48 +0800 Subject: [PATCH 026/273] fix: `map_unwrap_or` suggests wrongly for empty slice --- clippy_lints/src/derivable_impls.rs | 2 +- clippy_lints/src/methods/map_unwrap_or.rs | 231 ++++++++++-------- .../src/methods/map_unwrap_or_else.rs | 81 +++--- tests/ui/map_unwrap_or.rs | 8 + tests/ui/map_unwrap_or.stderr | 8 +- tests/ui/map_unwrap_or_fixable.fixed | 6 +- tests/ui/map_unwrap_or_fixable.rs | 6 +- tests/ui/map_unwrap_or_fixable.stderr | 46 ++-- 8 files changed, 211 insertions(+), 177 deletions(-) diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 6b8a6aec92fa..992ed320ce68 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -105,7 +105,7 @@ fn check_struct<'tcx>( if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind && let Some(PathSegment { args, .. }) = p.segments.last() { - let args = args.map(|a| a.args).unwrap_or(&[]); + let args = args.map(|a| a.args).unwrap_or_default(); // ty_args contains the generic parameters of the type declaration, while args contains the // arguments used at instantiation time. If both len are not equal, it means that some diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs index f2e3cc5c0091..ac2f99180486 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::MaybeDef; +use clippy_utils::res::{MaybeDef as _, MaybeResPath as _}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_copy; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::intravisit::{Visitor, walk_path}; -use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath}; +use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; +use rustc_hir::{ExprKind, HirId, LangItem, Node, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_span::{Span, sym}; @@ -28,116 +28,131 @@ pub(super) fn check<'tcx>( msrv: Msrv, ) { let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let is_option = recv_ty.is_diag_item(cx, sym::Option); - let is_result = recv_ty.is_diag_item(cx, sym::Result); + let recv_ty_kind = match recv_ty.opt_diag_name(cx) { + Some(sym::Option) => sym::Option, + Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR) => sym::Result, + _ => return, + }; - if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR) { + let unwrap_arg_ty = cx.typeck_results().expr_ty(unwrap_arg); + if !is_copy(cx, unwrap_arg_ty) { + // Replacing `.map().unwrap_or()` with `.map_or(, )` can sometimes lead to + // borrowck errors, see #10579 for one such instance. + // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint: + // ``` + // let x = vec![1, 2]; + // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x); + // ``` + // This compiles, but changing it to `map_or` will produce a compile error: + // ``` + // let x = vec![1, 2]; + // x.get(0..1).map_or(x, |s| s.to_vec()) + // ^ moving `x` here + // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure) + // ``` + // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!) + // before the call to `unwrap_or`. + + let mut unwrap_visitor = UnwrapVisitor { + cx, + identifiers: FxHashSet::default(), + }; + unwrap_visitor.visit_expr(unwrap_arg); + + let mut reference_visitor = ReferenceVisitor { + cx, + identifiers: unwrap_visitor.identifiers, + unwrap_or_span: unwrap_arg.span, + }; + + let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id)); + + // Visit the body, and return if we've found a reference + if reference_visitor.visit_body(body).is_break() { + return; + } + } + + if !unwrap_arg.span.eq_ctxt(map_span) { return; } - // lint if the caller of `map()` is an `Option` - if is_option || is_result { - if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) { - // Replacing `.map().unwrap_or()` with `.map_or(, )` can sometimes lead to - // borrowck errors, see #10579 for one such instance. - // In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint: - // ``` - // let x = vec![1, 2]; - // x.get(0..1).map(|s| s.to_vec()).unwrap_or(x); - // ``` - // This compiles, but changing it to `map_or` will produce a compile error: - // ``` - // let x = vec![1, 2]; - // x.get(0..1).map_or(x, |s| s.to_vec()) - // ^ moving `x` here - // ^^^^^^^^^^^ while it is borrowed here (and later used in the closure) - // ``` - // So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!) - // before the call to `unwrap_or`. + let mut applicability = Applicability::MachineApplicable; + // get snippet for unwrap_or() + let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability); + // lint message - let mut unwrap_visitor = UnwrapVisitor { - cx, - identifiers: FxHashSet::default(), - }; - unwrap_visitor.visit_expr(unwrap_arg); - - let mut reference_visitor = ReferenceVisitor { - cx, - identifiers: unwrap_visitor.identifiers, - unwrap_or_span: unwrap_arg.span, - }; - - let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id)); - - // Visit the body, and return if we've found a reference - if reference_visitor.visit_body(body).is_break() { - return; - } - } - - if !unwrap_arg.span.eq_ctxt(map_span) { - return; - } - - // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead - let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit) - if matches!(lit.node, rustc_ast::LitKind::Bool(false))) - && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND); - - let mut applicability = Applicability::MachineApplicable; - // get snippet for unwrap_or() - let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability); - // lint message - // comparing the snippet from source to raw text ("None") below is safe - // because we already have checked the type. - let unwrap_snippet_none = is_option && unwrap_snippet == "None"; - let arg = if unwrap_snippet_none { - "None" - } else if suggest_is_some_and { - "false" - } else { - "" - }; - let suggest = if unwrap_snippet_none { - "and_then()" - } else if suggest_is_some_and { - if is_result { - "is_ok_and()" - } else { - "is_some_and()" - } - } else { - "map_or(, )" - }; - let msg = format!( - "called `map().unwrap_or({arg})` on an `{}` value", - if is_option { "Option" } else { "Result" } - ); - - span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { - let map_arg_span = map_arg.span; - - let mut suggestion = vec![ - ( - map_span, - String::from(if unwrap_snippet_none { - "and_then" - } else if suggest_is_some_and { - if is_result { "is_ok_and" } else { "is_some_and" } - } else { - "map_or" - }), - ), - (expr.span.with_lo(unwrap_recv.span.hi()), String::new()), - ]; - - if !unwrap_snippet_none && !suggest_is_some_and { - suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, "))); - } - - diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability); - }); + let suggest_kind = if recv_ty_kind == sym::Option + && unwrap_arg + .basic_res() + .ctor_parent(cx) + .is_lang_item(cx, LangItem::OptionNone) + { + SuggestedKind::AndThen } + // is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead + else if matches!(&unwrap_arg.kind, ExprKind::Lit(lit) + if matches!(lit.node, rustc_ast::LitKind::Bool(false))) + && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) + { + SuggestedKind::IsVariantAnd + } else { + SuggestedKind::Other + }; + + let arg = match suggest_kind { + SuggestedKind::AndThen => "None", + SuggestedKind::IsVariantAnd => "false", + SuggestedKind::Other => "", + }; + + let suggest = match (suggest_kind, recv_ty_kind) { + (SuggestedKind::AndThen, _) => "and_then()", + (SuggestedKind::IsVariantAnd, sym::Result) => "is_ok_and()", + (SuggestedKind::IsVariantAnd, sym::Option) => "is_some_and()", + _ => "map_or(, )", + }; + + let msg = format!( + "called `map().unwrap_or({arg})` on {} `{recv_ty_kind}` value", + if recv_ty_kind == sym::Option { "an" } else { "a" } + ); + + span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| { + let map_arg_span = map_arg.span; + + let mut suggestion = vec![ + ( + map_span, + String::from(match (suggest_kind, recv_ty_kind) { + (SuggestedKind::AndThen, _) => "and_then", + (SuggestedKind::IsVariantAnd, sym::Result) => "is_ok_and", + (SuggestedKind::IsVariantAnd, sym::Option) => "is_some_and", + (SuggestedKind::Other, _) + if unwrap_arg_ty.peel_refs().is_array() + && cx.typeck_results().expr_ty_adjusted(unwrap_arg).peel_refs().is_slice() => + { + return; + }, + _ => "map_or", + }), + ), + (expr.span.with_lo(unwrap_recv.span.hi()), String::new()), + ]; + + if matches!(suggest_kind, SuggestedKind::Other) { + suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, "))); + } + + diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability); + }); +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum SuggestedKind { + AndThen, + IsVariantAnd, + Other, } struct UnwrapVisitor<'a, 'tcx> { @@ -186,7 +201,7 @@ impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { { return ControlFlow::Break(()); } - rustc_hir::intravisit::walk_expr(self, expr) + walk_expr(self, expr) } fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { diff --git a/clippy_lints/src/methods/map_unwrap_or_else.rs b/clippy_lints/src/methods/map_unwrap_or_else.rs index a2f157c0cb8a..8bb532b21635 100644 --- a/clippy_lints/src/methods/map_unwrap_or_else.rs +++ b/clippy_lints/src/methods/map_unwrap_or_else.rs @@ -1,18 +1,18 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::MaybeDef; +use clippy_utils::res::MaybeDef as _; use clippy_utils::source::snippet; +use clippy_utils::sym; use clippy_utils::usage::mutated_variables; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_span::symbol::sym; use super::MAP_UNWRAP_OR; /// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s /// -/// Returns true if the lint was emitted +/// Is part of the `map_unwrap_or` lint, split into separate files for readability. pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, @@ -22,49 +22,46 @@ pub(super) fn check<'tcx>( msrv: Msrv, ) -> bool { let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs(); - let is_option = recv_ty.is_diag_item(cx, sym::Option); - let is_result = recv_ty.is_diag_item(cx, sym::Result); + let recv_ty_kind = match recv_ty.opt_diag_name(cx) { + Some(sym::Option) => sym::Option, + Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) => sym::Result, + _ => return false, + }; - if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) { + // Don't make a suggestion that may fail to compile due to mutably borrowing + // the same variable twice. + let Some(map_mutated_vars) = mutated_variables(recv, cx) else { + return false; + }; + let Some(unwrap_mutated_vars) = mutated_variables(unwrap_arg, cx) else { + return false; + }; + if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { return false; } - if is_option || is_result { - // Don't make a suggestion that may fail to compile due to mutably borrowing - // the same variable twice. - let map_mutated_vars = mutated_variables(recv, cx); - let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx); - if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { - if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { - return false; - } - } else { - return false; - } - - // lint message - let msg = if is_option { - "called `map().unwrap_or_else()` on an `Option` value" - } else { - "called `map().unwrap_or_else()` on a `Result` value" - }; - // get snippets for args to map() and unwrap_or_else() - let map_snippet = snippet(cx, map_arg.span, ".."); - let unwrap_snippet = snippet(cx, unwrap_arg.span, ".."); - // lint, with note if both map() and unwrap_or_else() have the same span - if map_arg.span.eq_ctxt(unwrap_arg.span) { - let var_snippet = snippet(cx, recv.span, ".."); - span_lint_and_sugg( - cx, - MAP_UNWRAP_OR, - expr.span, - msg, - "try", - format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"), - Applicability::MachineApplicable, - ); - return true; - } + // lint message + let msg = if recv_ty_kind == sym::Option { + "called `map().unwrap_or_else()` on an `Option` value" + } else { + "called `map().unwrap_or_else()` on a `Result` value" + }; + // get snippets for args to map() and unwrap_or_else() + let map_snippet = snippet(cx, map_arg.span, ".."); + let unwrap_snippet = snippet(cx, unwrap_arg.span, ".."); + // lint, with note if both map() and unwrap_or_else() have the same span + if map_arg.span.eq_ctxt(unwrap_arg.span) { + let var_snippet = snippet(cx, recv.span, ".."); + span_lint_and_sugg( + cx, + MAP_UNWRAP_OR, + expr.span, + msg, + "try", + format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"), + Applicability::MachineApplicable, + ); + return true; } false diff --git a/tests/ui/map_unwrap_or.rs b/tests/ui/map_unwrap_or.rs index fba81cb493cd..dccacd7df8af 100644 --- a/tests/ui/map_unwrap_or.rs +++ b/tests/ui/map_unwrap_or.rs @@ -156,3 +156,11 @@ mod issue_10579 { println!("{y:?}"); } } + +fn issue15752() { + struct Foo<'a>(&'a [u32]); + + let x = Some(Foo(&[1, 2, 3])); + x.map(|y| y.0).unwrap_or(&[]); + //~^ map_unwrap_or +} diff --git a/tests/ui/map_unwrap_or.stderr b/tests/ui/map_unwrap_or.stderr index df0207c420e6..bd9e0eeb0bda 100644 --- a/tests/ui/map_unwrap_or.stderr +++ b/tests/ui/map_unwrap_or.stderr @@ -232,5 +232,11 @@ LL - let _ = opt.map(|x| x > 5).unwrap_or(false); LL + let _ = opt.is_some_and(|x| x > 5); | -error: aborting due to 15 previous errors +error: called `map().unwrap_or()` on an `Option` value + --> tests/ui/map_unwrap_or.rs:164:5 + | +LL | x.map(|y| y.0).unwrap_or(&[]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 16 previous errors diff --git a/tests/ui/map_unwrap_or_fixable.fixed b/tests/ui/map_unwrap_or_fixable.fixed index 1789c7f4d487..dca2536132d7 100644 --- a/tests/ui/map_unwrap_or_fixable.fixed +++ b/tests/ui/map_unwrap_or_fixable.fixed @@ -1,7 +1,11 @@ //@aux-build:option_helpers.rs #![warn(clippy::map_unwrap_or)] -#![allow(clippy::unnecessary_lazy_evaluations)] +#![allow( + clippy::unnecessary_lazy_evaluations, + clippy::manual_is_variant_and, + clippy::unnecessary_map_or +)] #[macro_use] extern crate option_helpers; diff --git a/tests/ui/map_unwrap_or_fixable.rs b/tests/ui/map_unwrap_or_fixable.rs index 309067edce4d..c60cb082ae3c 100644 --- a/tests/ui/map_unwrap_or_fixable.rs +++ b/tests/ui/map_unwrap_or_fixable.rs @@ -1,7 +1,11 @@ //@aux-build:option_helpers.rs #![warn(clippy::map_unwrap_or)] -#![allow(clippy::unnecessary_lazy_evaluations)] +#![allow( + clippy::unnecessary_lazy_evaluations, + clippy::manual_is_variant_and, + clippy::unnecessary_map_or +)] #[macro_use] extern crate option_helpers; diff --git a/tests/ui/map_unwrap_or_fixable.stderr b/tests/ui/map_unwrap_or_fixable.stderr index 696f516ee055..33a865d6769c 100644 --- a/tests/ui/map_unwrap_or_fixable.stderr +++ b/tests/ui/map_unwrap_or_fixable.stderr @@ -1,5 +1,5 @@ error: called `map().unwrap_or_else()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:17:13 + --> tests/ui/map_unwrap_or_fixable.rs:21:13 | LL | let _ = opt.map(|x| x + 1) | _____________^ @@ -11,7 +11,7 @@ LL | | .unwrap_or_else(|| 0); = help: to override `-D warnings` add `#[allow(clippy::map_unwrap_or)]` error: called `map().unwrap_or_else()` on a `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:48:13 + --> tests/ui/map_unwrap_or_fixable.rs:52:13 | LL | let _ = res.map(|x| x + 1) | _____________^ @@ -20,7 +20,7 @@ LL | | .unwrap_or_else(|_e| 0); | |_______________________________^ help: try: `res.map_or_else(|_e| 0, |x| x + 1)` error: called `map().unwrap_or()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:65:20 + --> tests/ui/map_unwrap_or_fixable.rs:69:20 | LL | println!("{}", o.map(|y| y + 1).unwrap_or(3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,13 +32,13 @@ LL + println!("{}", o.map_or(3, |y| y + 1)); | error: called `map().unwrap_or_else()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:67:20 + --> tests/ui/map_unwrap_or_fixable.rs:71:20 | LL | println!("{}", o.map(|y| y + 1).unwrap_or_else(|| 3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `o.map_or_else(|| 3, |y| y + 1)` -error: called `map().unwrap_or()` on an `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:69:20 +error: called `map().unwrap_or()` on a `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:73:20 | LL | println!("{}", r.map(|y| y + 1).unwrap_or(3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -50,13 +50,13 @@ LL + println!("{}", r.map_or(3, |y| y + 1)); | error: called `map().unwrap_or_else()` on a `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:71:20 + --> tests/ui/map_unwrap_or_fixable.rs:75:20 | LL | println!("{}", r.map(|y| y + 1).unwrap_or_else(|()| 3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `r.map_or_else(|()| 3, |y| y + 1)` -error: called `map().unwrap_or(false)` on an `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:74:20 +error: called `map().unwrap_or(false)` on a `Result` value + --> tests/ui/map_unwrap_or_fixable.rs:78:20 | LL | println!("{}", r.map(|y| y == 1).unwrap_or(false)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -68,18 +68,6 @@ LL + println!("{}", r.is_ok_and(|y| y == 1)); | error: called `map().unwrap_or()` on an `Option` value - --> tests/ui/map_unwrap_or_fixable.rs:80:20 - | -LL | println!("{}", x.map(|y| y + 1).unwrap_or(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: use `map_or(, )` instead - | -LL - println!("{}", x.map(|y| y + 1).unwrap_or(3)); -LL + println!("{}", x.map_or(3, |y| y + 1)); - | - -error: called `map().unwrap_or()` on an `Result` value --> tests/ui/map_unwrap_or_fixable.rs:84:20 | LL | println!("{}", x.map(|y| y + 1).unwrap_or(3)); @@ -91,14 +79,26 @@ LL - println!("{}", x.map(|y| y + 1).unwrap_or(3)); LL + println!("{}", x.map_or(3, |y| y + 1)); | -error: called `map().unwrap_or_else()` on an `Option` value +error: called `map().unwrap_or()` on a `Result` value --> tests/ui/map_unwrap_or_fixable.rs:88:20 | +LL | println!("{}", x.map(|y| y + 1).unwrap_or(3)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `map_or(, )` instead + | +LL - println!("{}", x.map(|y| y + 1).unwrap_or(3)); +LL + println!("{}", x.map_or(3, |y| y + 1)); + | + +error: called `map().unwrap_or_else()` on an `Option` value + --> tests/ui/map_unwrap_or_fixable.rs:92:20 + | LL | println!("{}", x.map(|y| y + 1).unwrap_or_else(|| 3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map_or_else(|| 3, |y| y + 1)` error: called `map().unwrap_or_else()` on a `Result` value - --> tests/ui/map_unwrap_or_fixable.rs:92:20 + --> tests/ui/map_unwrap_or_fixable.rs:96:20 | LL | println!("{}", x.map(|y| y + 1).unwrap_or_else(|_| 3)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map_or_else(|_| 3, |y| y + 1)` From 4976fdf203b5122bf3b26acf02af5ae6ea25f138 Mon Sep 17 00:00:00 2001 From: Linshu Yang Date: Wed, 7 Jan 2026 23:17:33 +0000 Subject: [PATCH 027/273] fix: `significant_drop_tightening` suggests wrongly for non-method usage --- .../src/significant_drop_tightening.rs | 24 ++++--- tests/ui/significant_drop_tightening.fixed | 36 ++++++++++ tests/ui/significant_drop_tightening.rs | 33 +++++++++ tests/ui/significant_drop_tightening.stderr | 71 ++++++++++++++++++- 4 files changed, 152 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index fabb21f78b9e..8557e8d18d10 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; +use rustc_ast::BindingMode; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -89,13 +90,14 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { |diag| { match apa.counter { 0 | 1 => {}, - 2 => { + 2 if let Some(last_method_span) = apa.last_method_span => { let indent = " ".repeat(indent_of(cx, apa.last_stmt_span).unwrap_or(0)); let init_method = snippet(cx, apa.first_method_span, ".."); - let usage_method = snippet(cx, apa.last_method_span, ".."); - let stmt = if let Some(last_bind_ident) = apa.last_bind_ident { + let usage_method = snippet(cx, last_method_span, ".."); + let stmt = if let Some((binding_mode, last_bind_ident)) = apa.last_bind_ident { format!( - "\n{indent}let {} = {init_method}.{usage_method};", + "\n{indent}let {}{} = {init_method}.{usage_method};", + binding_mode.prefix_str(), snippet(cx, last_bind_ident.span, ".."), ) } else { @@ -310,13 +312,13 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { }; match self.ap.curr_stmt.kind { hir::StmtKind::Let(local) => { - if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { - apa.last_bind_ident = Some(ident); + if let hir::PatKind::Binding(binding_mode, _, ident, _) = local.pat.kind { + apa.last_bind_ident = Some((binding_mode, ident)); } if let Some(local_init) = local.init && let hir::ExprKind::MethodCall(_, _, _, span) = local_init.kind { - apa.last_method_span = span; + apa.last_method_span = Some(span); } }, hir::StmtKind::Semi(semi_expr) => { @@ -326,7 +328,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { return; } if let hir::ExprKind::MethodCall(_, _, _, span) = semi_expr.kind { - apa.last_method_span = span; + apa.last_method_span = Some(span); } }, _ => {}, @@ -385,9 +387,9 @@ struct AuxParamsAttr { /// The last visited binding or variable span within a block that had any referenced inner type /// marked with `#[has_significant_drop]`. - last_bind_ident: Option, + last_bind_ident: Option<(BindingMode, Ident)>, /// Similar to `last_bind_span` but encompasses the right-hand method call. - last_method_span: Span, + last_method_span: Option, /// Similar to `last_bind_span` but encompasses the whole contained statement. last_stmt_span: Span, } @@ -403,7 +405,7 @@ impl Default for AuxParamsAttr { first_method_span: DUMMY_SP, first_stmt_span: DUMMY_SP, last_bind_ident: None, - last_method_span: DUMMY_SP, + last_method_span: None, last_stmt_span: DUMMY_SP, } } diff --git a/tests/ui/significant_drop_tightening.fixed b/tests/ui/significant_drop_tightening.fixed index 3d416056226c..559c1bb94570 100644 --- a/tests/ui/significant_drop_tightening.fixed +++ b/tests/ui/significant_drop_tightening.fixed @@ -146,3 +146,39 @@ pub fn unnecessary_contention_with_single_owned_results() { pub fn do_heavy_computation_that_takes_time(_: T) {} fn main() {} + +fn issue15574() { + use std::io::{BufRead, Read, stdin}; + use std::process; + + println!("Hello, what's your name?"); + + let mut stdin = stdin().lock().take(40); + //~^ significant_drop_tightening + let mut buffer = String::with_capacity(10); + + + //~^ significant_drop_tightening + if stdin.read_line(&mut buffer).is_err() { + eprintln!("An error has occured while reading."); + return; + } + drop(stdin); + println!("Our string has a capacity of {}", buffer.capacity()); + println!("Hello {}!", buffer); +} + +fn issue16343() { + fn get_items(x: &()) -> Vec<()> { + vec![*x] + } + + let storage = Mutex::new(()); + let lock = storage.lock().unwrap(); + //~^ significant_drop_tightening + let items = get_items(&lock); + drop(lock); + for item in items { + println!("item {:?}", item); + } +} diff --git a/tests/ui/significant_drop_tightening.rs b/tests/ui/significant_drop_tightening.rs index d9c4ad543593..dff36baf383d 100644 --- a/tests/ui/significant_drop_tightening.rs +++ b/tests/ui/significant_drop_tightening.rs @@ -142,3 +142,36 @@ pub fn unnecessary_contention_with_single_owned_results() { pub fn do_heavy_computation_that_takes_time(_: T) {} fn main() {} + +fn issue15574() { + use std::io::{BufRead, Read, stdin}; + use std::process; + + println!("Hello, what's your name?"); + let stdin = stdin().lock(); + //~^ significant_drop_tightening + let mut buffer = String::with_capacity(10); + + let mut stdin = stdin.take(40); + //~^ significant_drop_tightening + if stdin.read_line(&mut buffer).is_err() { + eprintln!("An error has occured while reading."); + return; + } + println!("Our string has a capacity of {}", buffer.capacity()); + println!("Hello {}!", buffer); +} + +fn issue16343() { + fn get_items(x: &()) -> Vec<()> { + vec![*x] + } + + let storage = Mutex::new(()); + let lock = storage.lock().unwrap(); + //~^ significant_drop_tightening + let items = get_items(&lock); + for item in items { + println!("item {:?}", item); + } +} diff --git a/tests/ui/significant_drop_tightening.stderr b/tests/ui/significant_drop_tightening.stderr index 25cd9da73a10..785bee4970ec 100644 --- a/tests/ui/significant_drop_tightening.stderr +++ b/tests/ui/significant_drop_tightening.stderr @@ -83,5 +83,74 @@ LL | LL ~ | -error: aborting due to 4 previous errors +error: temporary with significant `Drop` can be early dropped + --> tests/ui/significant_drop_tightening.rs:151:9 + | +LL | fn issue15574() { + | _________________- +LL | | use std::io::{BufRead, Read, stdin}; +LL | | use std::process; +... | +LL | | let stdin = stdin().lock(); + | | ^^^^^ +... | +LL | | println!("Hello {}!", buffer); +LL | | } + | |_- temporary `stdin` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: merge the temporary construction with its single usage + | +LL ~ +LL + let mut stdin = stdin().lock().take(40); +LL | +LL | let mut buffer = String::with_capacity(10); +LL | +LL ~ + | + +error: temporary with significant `Drop` can be early dropped + --> tests/ui/significant_drop_tightening.rs:155:13 + | +LL | fn issue15574() { + | _________________- +LL | | use std::io::{BufRead, Read, stdin}; +LL | | use std::process; +... | +LL | | let mut stdin = stdin.take(40); + | | ^^^^^ +... | +LL | | println!("Hello {}!", buffer); +LL | | } + | |_- temporary `stdin` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: drop the temporary after the end of its last usage + | +LL ~ } +LL + drop(stdin); + | + +error: temporary with significant `Drop` can be early dropped + --> tests/ui/significant_drop_tightening.rs:171:9 + | +LL | fn issue16343() { + | _________________- +LL | | fn get_items(x: &()) -> Vec<()> { +LL | | vec![*x] +... | +LL | | let lock = storage.lock().unwrap(); + | | ^^^^ +... | +LL | | } + | |_- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: drop the temporary after the end of its last usage + | +LL ~ let items = get_items(&lock); +LL + drop(lock); + | + +error: aborting due to 7 previous errors From 72f1ac9fbdbff5b432519891ac3d826a0333cf34 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 12 Mar 2025 10:26:37 +0000 Subject: [PATCH 028/273] Compile-Time Reflection MVP: tuples --- clippy_utils/src/sym.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index a0d2e8673fe6..74f89cfc6811 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -62,7 +62,6 @@ generate! { MsrvStack, Octal, OpenOptions, - Other, PathLookup, Regex, RegexBuilder, From 21f6afca40cb423b4920fbc571490f801aa95d16 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 8 Jan 2026 14:10:33 +0100 Subject: [PATCH 029/273] Update `literal-escaper` version to `0.0.7` --- clippy_dev/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index c2abbac37535..238465210ee2 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -10,7 +10,7 @@ clap = { version = "4.4", features = ["derive"] } indoc = "1.0" itertools = "0.12" opener = "0.7" -rustc-literal-escaper = "0.0.5" +rustc-literal-escaper = "0.0.7" walkdir = "2.3" [package.metadata.rust-analyzer] From fb5c85071b47a7d1c1c5db9381c0e8722507d350 Mon Sep 17 00:00:00 2001 From: human9000 Date: Mon, 5 Jan 2026 18:14:12 +0500 Subject: [PATCH 030/273] MGCA: literals support --- clippy_lints/src/utils/author.rs | 1 + clippy_utils/src/consts.rs | 2 +- clippy_utils/src/hir_utils.rs | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 455f76edc904..7a54ba7a8fe1 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -324,6 +324,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { ConstArgKind::Tup(..) => chain!(self, "let ConstArgKind::Tup(..) = {const_arg}.kind"), ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"), ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"), + ConstArgKind::Literal(..) => chain!(self, "let ConstArgKind::Literal(..) = {const_arg}.kind") } } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 46b87fd5df96..5f4b87590dc1 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1140,7 +1140,7 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), - ConstArgKind::Struct(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Tup(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => { + ConstArgKind::Struct(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Tup(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) | ConstArgKind::Literal(..) => { None }, }, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 57c896c97172..b4e483ea8072 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -689,6 +689,9 @@ impl HirEqInterExpr<'_, '_, '_> { .zip(*args_b) .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) } + (ConstArgKind::Literal(kind_l), ConstArgKind::Literal(kind_r)) => { + kind_l == kind_r + }, // Use explicit match for now since ConstArg is undergoing flux. ( ConstArgKind::Path(..) @@ -697,6 +700,7 @@ impl HirEqInterExpr<'_, '_, '_> { | ConstArgKind::Tup(..) | ConstArgKind::Infer(..) | ConstArgKind::Struct(..) + | ConstArgKind::Literal(..) | ConstArgKind::Error(..), _, ) => false, @@ -1577,6 +1581,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } }, ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {}, + ConstArgKind::Literal(lit) => lit.hash(&mut self.s) } } From 331f75f4a773fadba135664190905457946ea98e Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 8 Jan 2026 16:37:55 +0100 Subject: [PATCH 031/273] Do not warn on arithmetic side effect for `String`+`String` The previous fix only handled `String`+`str`. --- .../src/operators/arithmetic_side_effects.rs | 5 +- tests/ui/arithmetic_side_effects.rs | 1 - tests/ui/arithmetic_side_effects.stderr | 266 +++++++++--------- 3 files changed, 134 insertions(+), 138 deletions(-) diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 91a069559f7b..106286d16d18 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -33,7 +33,10 @@ impl ArithmeticSideEffects { allowed_binary.extend([ ("f32", FxHashSet::from_iter(["f32"])), ("f64", FxHashSet::from_iter(["f64"])), - ("std::string::String", FxHashSet::from_iter(["str"])), + ( + "std::string::String", + FxHashSet::from_iter(["str", "std::string::String"]), + ), ]); for (lhs, rhs) in &conf.arithmetic_side_effects_allowed_binary { allowed_binary.entry(lhs).or_default().insert(rhs); diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index b7ed596d811e..87397a549bf4 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -174,7 +174,6 @@ pub fn hard_coded_allowed() { let _ = Saturating(0u32) + Saturating(0u32); let _ = String::new() + ""; let _ = String::new() + &String::new(); - //~^ arithmetic_side_effects let _ = Wrapping(0u32) + Wrapping(0u32); let saturating: Saturating = Saturating(0u32); diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 22742a82601a..2767a051786e 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -14,784 +14,778 @@ LL | let _ = 1f128 + 1f128; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:176:13 - | -LL | let _ = String::new() + &String::new(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:312:5 + --> tests/ui/arithmetic_side_effects.rs:311:5 | LL | _n += 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:314:5 + --> tests/ui/arithmetic_side_effects.rs:313:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:316:5 + --> tests/ui/arithmetic_side_effects.rs:315:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:318:5 + --> tests/ui/arithmetic_side_effects.rs:317:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:320:5 + --> tests/ui/arithmetic_side_effects.rs:319:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:322:5 + --> tests/ui/arithmetic_side_effects.rs:321:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:324:5 + --> tests/ui/arithmetic_side_effects.rs:323:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:326:5 + --> tests/ui/arithmetic_side_effects.rs:325:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:328:5 + --> tests/ui/arithmetic_side_effects.rs:327:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:330:5 + --> tests/ui/arithmetic_side_effects.rs:329:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:332:5 + --> tests/ui/arithmetic_side_effects.rs:331:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:334:5 + --> tests/ui/arithmetic_side_effects.rs:333:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:336:5 + --> tests/ui/arithmetic_side_effects.rs:335:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:338:5 + --> tests/ui/arithmetic_side_effects.rs:337:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:340:5 + --> tests/ui/arithmetic_side_effects.rs:339:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:342:5 + --> tests/ui/arithmetic_side_effects.rs:341:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:344:5 + --> tests/ui/arithmetic_side_effects.rs:343:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:346:5 + --> tests/ui/arithmetic_side_effects.rs:345:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:348:5 + --> tests/ui/arithmetic_side_effects.rs:347:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:350:5 + --> tests/ui/arithmetic_side_effects.rs:349:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:352:5 + --> tests/ui/arithmetic_side_effects.rs:351:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:354:5 + --> tests/ui/arithmetic_side_effects.rs:353:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:356:5 + --> tests/ui/arithmetic_side_effects.rs:355:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:358:5 + --> tests/ui/arithmetic_side_effects.rs:357:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:360:5 + --> tests/ui/arithmetic_side_effects.rs:359:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:362:5 + --> tests/ui/arithmetic_side_effects.rs:361:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:364:5 + --> tests/ui/arithmetic_side_effects.rs:363:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:366:5 + --> tests/ui/arithmetic_side_effects.rs:365:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:368:5 + --> tests/ui/arithmetic_side_effects.rs:367:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:370:5 + --> tests/ui/arithmetic_side_effects.rs:369:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:372:5 + --> tests/ui/arithmetic_side_effects.rs:371:5 | LL | _custom >>= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:374:5 + --> tests/ui/arithmetic_side_effects.rs:373:5 | LL | _custom >>= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:376:5 + --> tests/ui/arithmetic_side_effects.rs:375:5 | LL | _custom <<= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:378:5 + --> tests/ui/arithmetic_side_effects.rs:377:5 | LL | _custom <<= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:380:5 + --> tests/ui/arithmetic_side_effects.rs:379:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:382:5 + --> tests/ui/arithmetic_side_effects.rs:381:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:384:5 + --> tests/ui/arithmetic_side_effects.rs:383:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:386:5 + --> tests/ui/arithmetic_side_effects.rs:385:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:388:5 + --> tests/ui/arithmetic_side_effects.rs:387:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:390:5 + --> tests/ui/arithmetic_side_effects.rs:389:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:392:5 + --> tests/ui/arithmetic_side_effects.rs:391:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:394:5 + --> tests/ui/arithmetic_side_effects.rs:393:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:396:5 + --> tests/ui/arithmetic_side_effects.rs:395:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:398:5 + --> tests/ui/arithmetic_side_effects.rs:397:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:400:5 + --> tests/ui/arithmetic_side_effects.rs:399:5 | LL | _custom >>= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:402:5 + --> tests/ui/arithmetic_side_effects.rs:401:5 | LL | _custom >>= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:404:5 + --> tests/ui/arithmetic_side_effects.rs:403:5 | LL | _custom <<= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:406:5 + --> tests/ui/arithmetic_side_effects.rs:405:5 | LL | _custom <<= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:410:10 + --> tests/ui/arithmetic_side_effects.rs:409:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:412:10 + --> tests/ui/arithmetic_side_effects.rs:411:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:414:10 + --> tests/ui/arithmetic_side_effects.rs:413:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:416:10 + --> tests/ui/arithmetic_side_effects.rs:415:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:418:10 + --> tests/ui/arithmetic_side_effects.rs:417:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:420:10 + --> tests/ui/arithmetic_side_effects.rs:419:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:422:10 + --> tests/ui/arithmetic_side_effects.rs:421:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:424:10 + --> tests/ui/arithmetic_side_effects.rs:423:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:426:10 + --> tests/ui/arithmetic_side_effects.rs:425:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:428:10 + --> tests/ui/arithmetic_side_effects.rs:427:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:430:10 + --> tests/ui/arithmetic_side_effects.rs:429:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:432:10 + --> tests/ui/arithmetic_side_effects.rs:431:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:434:10 + --> tests/ui/arithmetic_side_effects.rs:433:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:436:10 + --> tests/ui/arithmetic_side_effects.rs:435:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:438:10 + --> tests/ui/arithmetic_side_effects.rs:437:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:440:10 + --> tests/ui/arithmetic_side_effects.rs:439:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:442:10 + --> tests/ui/arithmetic_side_effects.rs:441:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:444:10 + --> tests/ui/arithmetic_side_effects.rs:443:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:446:10 + --> tests/ui/arithmetic_side_effects.rs:445:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:448:15 + --> tests/ui/arithmetic_side_effects.rs:447:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:450:15 + --> tests/ui/arithmetic_side_effects.rs:449:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:452:15 + --> tests/ui/arithmetic_side_effects.rs:451:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:454:15 + --> tests/ui/arithmetic_side_effects.rs:453:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:456:15 + --> tests/ui/arithmetic_side_effects.rs:455:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:458:15 + --> tests/ui/arithmetic_side_effects.rs:457:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:460:15 + --> tests/ui/arithmetic_side_effects.rs:459:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:462:15 + --> tests/ui/arithmetic_side_effects.rs:461:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:464:15 + --> tests/ui/arithmetic_side_effects.rs:463:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:466:15 + --> tests/ui/arithmetic_side_effects.rs:465:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:468:15 + --> tests/ui/arithmetic_side_effects.rs:467:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:470:15 + --> tests/ui/arithmetic_side_effects.rs:469:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:472:15 + --> tests/ui/arithmetic_side_effects.rs:471:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:474:15 + --> tests/ui/arithmetic_side_effects.rs:473:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:476:15 + --> tests/ui/arithmetic_side_effects.rs:475:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:478:15 + --> tests/ui/arithmetic_side_effects.rs:477:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:480:15 + --> tests/ui/arithmetic_side_effects.rs:479:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:482:15 + --> tests/ui/arithmetic_side_effects.rs:481:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:484:15 + --> tests/ui/arithmetic_side_effects.rs:483:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:486:15 + --> tests/ui/arithmetic_side_effects.rs:485:15 | LL | _custom = _custom >> _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:488:15 + --> tests/ui/arithmetic_side_effects.rs:487:15 | LL | _custom = _custom >> &_custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:490:15 + --> tests/ui/arithmetic_side_effects.rs:489:15 | LL | _custom = Custom << _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:492:15 + --> tests/ui/arithmetic_side_effects.rs:491:15 | LL | _custom = &Custom << _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:496:23 + --> tests/ui/arithmetic_side_effects.rs:495:23 | LL | _n.saturating_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:498:21 + --> tests/ui/arithmetic_side_effects.rs:497:21 | LL | _n.wrapping_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:500:21 + --> tests/ui/arithmetic_side_effects.rs:499:21 | LL | _n.wrapping_rem(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:502:28 + --> tests/ui/arithmetic_side_effects.rs:501:28 | LL | _n.wrapping_rem_euclid(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:505:23 + --> tests/ui/arithmetic_side_effects.rs:504:23 | LL | _n.saturating_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:507:21 + --> tests/ui/arithmetic_side_effects.rs:506:21 | LL | _n.wrapping_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:509:21 + --> tests/ui/arithmetic_side_effects.rs:508:21 | LL | _n.wrapping_rem(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:511:28 + --> tests/ui/arithmetic_side_effects.rs:510:28 | LL | _n.wrapping_rem_euclid(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:514:23 + --> tests/ui/arithmetic_side_effects.rs:513:23 | LL | _n.saturating_div(*Box::new(_n)); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:518:10 + --> tests/ui/arithmetic_side_effects.rs:517:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:520:10 + --> tests/ui/arithmetic_side_effects.rs:519:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:522:15 + --> tests/ui/arithmetic_side_effects.rs:521:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:524:15 + --> tests/ui/arithmetic_side_effects.rs:523:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:526:9 + --> tests/ui/arithmetic_side_effects.rs:525:9 | LL | _ = -*Box::new(_n); | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:536:5 + --> tests/ui/arithmetic_side_effects.rs:535:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:538:5 + --> tests/ui/arithmetic_side_effects.rs:537:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:540:5 + --> tests/ui/arithmetic_side_effects.rs:539:5 | LL | 1 % i / 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:542:5 + --> tests/ui/arithmetic_side_effects.rs:541:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:544:5 + --> tests/ui/arithmetic_side_effects.rs:543:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:556:5 + --> tests/ui/arithmetic_side_effects.rs:555:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:558:5 + --> tests/ui/arithmetic_side_effects.rs:557:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:560:5 + --> tests/ui/arithmetic_side_effects.rs:559:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:563:5 + --> tests/ui/arithmetic_side_effects.rs:562:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:566:5 + --> tests/ui/arithmetic_side_effects.rs:565:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:568:5 + --> tests/ui/arithmetic_side_effects.rs:567:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:571:5 + --> tests/ui/arithmetic_side_effects.rs:570:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:574:5 + --> tests/ui/arithmetic_side_effects.rs:573:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:576:5 + --> tests/ui/arithmetic_side_effects.rs:575:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:587:5 + --> tests/ui/arithmetic_side_effects.rs:586:5 | LL | 10 / a | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:642:9 + --> tests/ui/arithmetic_side_effects.rs:641:9 | LL | x / maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:647:9 + --> tests/ui/arithmetic_side_effects.rs:646:9 | LL | x % maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:659:5 + --> tests/ui/arithmetic_side_effects.rs:658:5 | LL | one.add_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:664:5 + --> tests/ui/arithmetic_side_effects.rs:663:5 | LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:685:5 + --> tests/ui/arithmetic_side_effects.rs:684:5 | LL | one.add(&one); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:687:5 + --> tests/ui/arithmetic_side_effects.rs:686:5 | LL | Box::new(one).add(one); | ^^^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:696:13 + --> tests/ui/arithmetic_side_effects.rs:695:13 | LL | let _ = u128::MAX + u128::from(1u8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:713:13 + --> tests/ui/arithmetic_side_effects.rs:712:13 | LL | let _ = u128::MAX * u128::from(1u8); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:736:33 + --> tests/ui/arithmetic_side_effects.rs:735:33 | LL | let _ = Duration::from_secs(86400 * Foo::from(1)); | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:742:33 + --> tests/ui/arithmetic_side_effects.rs:741:33 | LL | let _ = Duration::from_secs(86400 * shift(1)); | ^^^^^^^^^^^^^^^^ -error: aborting due to 132 previous errors +error: aborting due to 131 previous errors From a4f0937a9a28b4e1faacfc3ae1281e568d0ad59c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 7 Jan 2026 11:03:40 +0100 Subject: [PATCH 032/273] clean-up --- .../src/methods/unnecessary_sort_by.rs | 71 ++++++++----------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index 4a3e4c092f3b..dbaa801eaf50 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -9,7 +9,7 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_middle::ty::GenericArgKind; use rustc_span::sym; -use rustc_span::symbol::Ident; +use rustc_span::symbol::{Ident, Symbol}; use std::iter; use super::UNNECESSARY_SORT_BY; @@ -20,29 +20,29 @@ enum LintTrigger { } struct SortDetection { - vec_name: String, + vec_name: Sugg<'static>, } struct SortByKeyDetection { - vec_name: String, - closure_arg: String, - closure_body: String, + vec_name: Sugg<'static>, + closure_arg: Symbol, + closure_body: Sugg<'static>, reverse: bool, } /// Detect if the two expressions are mirrored (identical, except one /// contains a and the other replaces it with b) -fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool { - match (&a_expr.kind, &b_expr.kind) { +fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: Ident, b_expr: &Expr<'_>, b_ident: Ident) -> bool { + match (a_expr.kind, b_expr.kind) { // Two arrays with mirrored contents (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => { - iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) + iter::zip(left_exprs, right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) }, // The two exprs are function calls. // Check to see that the function itself and its arguments are mirrored (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => { mirrored_exprs(left_expr, a_ident, right_expr, b_ident) - && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) + && iter::zip(left_args, right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) }, // The two exprs are method calls. // Check to see that the function is the same and the arguments and receivers are mirrored @@ -51,12 +51,12 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident ExprKind::MethodCall(right_segment, right_receiver, right_args, _), ) => { left_segment.ident == right_segment.ident - && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) + && iter::zip(left_args, right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) && mirrored_exprs(left_receiver, a_ident, right_receiver, b_ident) }, // Two tuples with mirrored contents (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => { - iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) + iter::zip(left_exprs, right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) }, // Two binary ops, which are the same operation and which have mirrored arguments (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => { @@ -81,27 +81,27 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident ( ExprKind::Path(QPath::Resolved( _, - Path { + &Path { segments: left_segments, .. }, )), ExprKind::Path(QPath::Resolved( _, - Path { + &Path { segments: right_segments, .. }, )), ) => { - (iter::zip(*left_segments, *right_segments).all(|(left, right)| left.ident == right.ident) + (iter::zip(left_segments, right_segments).all(|(left, right)| left.ident == right.ident) && left_segments .iter() - .all(|seg| &seg.ident != a_ident && &seg.ident != b_ident)) + .all(|seg| seg.ident != a_ident && seg.ident != b_ident)) || (left_segments.len() == 1 - && &left_segments[0].ident == a_ident + && left_segments[0].ident == a_ident && right_segments.len() == 1 - && &right_segments[0].ident == b_ident) + && right_segments[0].ident == b_ident) }, // Matching expressions, but one or both is borrowed ( @@ -123,7 +123,7 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp && let &[ Param { pat: - Pat { + &Pat { kind: PatKind::Binding(_, _, left_ident, _), .. }, @@ -131,36 +131,26 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp }, Param { pat: - Pat { + &Pat { kind: PatKind::Binding(_, _, right_ident, _), .. }, .. }, - ] = &closure_body.params + ] = closure_body.params && let ExprKind::MethodCall(method_path, left_expr, [right_expr], _) = closure_body.value.kind && method_path.ident.name == sym::cmp - && cx - .ty_based_def(closure_body.value) - .opt_parent(cx) - .is_diag_item(cx, sym::Ord) + && let Some(ord_trait) = cx.tcx.get_diagnostic_item(sym::Ord) + && cx.ty_based_def(closure_body.value).opt_parent(cx).opt_def_id() == Some(ord_trait) { let (closure_body, closure_arg, reverse) = if mirrored_exprs(left_expr, left_ident, right_expr, right_ident) { - ( - Sugg::hir(cx, left_expr, "..").to_string(), - left_ident.name.to_string(), - false, - ) + (Sugg::hir(cx, left_expr, "_"), left_ident.name, false) } else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) { - ( - Sugg::hir(cx, left_expr, "..").to_string(), - right_ident.name.to_string(), - true, - ) + (Sugg::hir(cx, left_expr, "_"), right_ident.name, true) } else { return None; }; - let vec_name = Sugg::hir(cx, recv, "..").to_string(); + let vec_name = Sugg::hir(cx, recv, "(_)"); if let ExprKind::Path(QPath::Resolved( _, @@ -168,12 +158,9 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp segments: [PathSegment { ident: left_name, .. }], .. }, - )) = &left_expr.kind - && left_name == left_ident - && cx - .tcx - .get_diagnostic_item(sym::Ord) - .is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[])) + )) = left_expr.kind + && *left_name == left_ident + && implements_trait(cx, cx.typeck_results().expr_ty(left_expr), ord_trait, &[]) { return Some(LintTrigger::Sort(SortDetection { vec_name })); } @@ -226,7 +213,7 @@ pub(super) fn check<'tcx>( { format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body) } else { - trigger.closure_body + trigger.closure_body.to_string() }, ), if trigger.reverse { From 0cfc4ee541d819e7cf93cdebb1c43fd0d9eb444b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 7 Jan 2026 16:25:41 +0100 Subject: [PATCH 033/273] fix: respect applicability reduction due to `Sugg` By postponing the creation of `Sugg`s, we can properly account for their effect on applicability --- .../src/methods/unnecessary_sort_by.rs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index dbaa801eaf50..c8cb85c05156 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::std_or_core; use clippy_utils::sugg::Sugg; @@ -14,19 +14,14 @@ use std::iter; use super::UNNECESSARY_SORT_BY; -enum LintTrigger { - Sort(SortDetection), - SortByKey(SortByKeyDetection), +enum LintTrigger<'tcx> { + Sort, + SortByKey(SortByKeyDetection<'tcx>), } -struct SortDetection { - vec_name: Sugg<'static>, -} - -struct SortByKeyDetection { - vec_name: Sugg<'static>, +struct SortByKeyDetection<'tcx> { closure_arg: Symbol, - closure_body: Sugg<'static>, + closure_body: &'tcx Expr<'tcx>, reverse: bool, } @@ -114,7 +109,7 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: Ident, b_expr: &Expr<'_>, b_ident: } } -fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) -> Option { +fn detect_lint<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, arg: &Expr<'_>) -> Option> { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() @@ -144,13 +139,12 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp && cx.ty_based_def(closure_body.value).opt_parent(cx).opt_def_id() == Some(ord_trait) { let (closure_body, closure_arg, reverse) = if mirrored_exprs(left_expr, left_ident, right_expr, right_ident) { - (Sugg::hir(cx, left_expr, "_"), left_ident.name, false) + (left_expr, left_ident.name, false) } else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) { - (Sugg::hir(cx, left_expr, "_"), right_ident.name, true) + (left_expr, right_ident.name, true) } else { return None; }; - let vec_name = Sugg::hir(cx, recv, "(_)"); if let ExprKind::Path(QPath::Resolved( _, @@ -162,12 +156,11 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp && *left_name == left_ident && implements_trait(cx, cx.typeck_results().expr_ty(left_expr), ord_trait, &[]) { - return Some(LintTrigger::Sort(SortDetection { vec_name })); + return Some(LintTrigger::Sort); } if !expr_borrows(cx, left_expr) { return Some(LintTrigger::SortByKey(SortByKeyDetection { - vec_name, closure_arg, closure_body, reverse, @@ -190,49 +183,57 @@ pub(super) fn check<'tcx>( arg: &'tcx Expr<'_>, is_unstable: bool, ) { - match detect_lint(cx, expr, recv, arg) { + match detect_lint(cx, expr, arg) { Some(LintTrigger::SortByKey(trigger)) => { let method = if is_unstable { "sort_unstable_by_key" } else { "sort_by_key" }; - span_lint_and_sugg( + span_lint_and_then( cx, UNNECESSARY_SORT_BY, expr.span, format!("consider using `{method}`"), - "try", - format!( - "{}.{}(|{}| {})", - trigger.vec_name, - method, - trigger.closure_arg, - if let Some(std_or_core) = std_or_core(cx) - && trigger.reverse - { - format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body) + |diag| { + let mut app = if trigger.reverse { + Applicability::MaybeIncorrect } else { - trigger.closure_body.to_string() - }, - ), - if trigger.reverse { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable + Applicability::MachineApplicable + }; + let recv = Sugg::hir_with_applicability(cx, recv, "(_)", &mut app); + let closure_body = Sugg::hir_with_applicability(cx, trigger.closure_body, "_", &mut app); + diag.span_suggestion( + expr.span, + "try", + format!( + "{recv}.{method}(|{}| {})", + trigger.closure_arg, + if let Some(std_or_core) = std_or_core(cx) + && trigger.reverse + { + format!("{std_or_core}::cmp::Reverse({closure_body})") + } else { + closure_body.to_string() + }, + ), + app, + ); }, ); }, - Some(LintTrigger::Sort(trigger)) => { + Some(LintTrigger::Sort) => { let method = if is_unstable { "sort_unstable" } else { "sort" }; - span_lint_and_sugg( + span_lint_and_then( cx, UNNECESSARY_SORT_BY, expr.span, format!("consider using `{method}`"), - "try", - format!("{}.{}()", trigger.vec_name, method), - Applicability::MachineApplicable, + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, recv, "(_)", &mut app); + diag.span_suggestion(expr.span, "try", format!("{recv}.{method}()"), app); + }, ); }, None => {}, From bdb1050a0f433be29982de0668102ec15a2c9b96 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 7 Jan 2026 16:42:15 +0100 Subject: [PATCH 034/273] fix: don't lint if `std` or `core` are required for a suggestion but unavailable --- .../src/methods/unnecessary_sort_by.rs | 23 ++++++++-------- tests/ui/unnecessary_sort_by_no_core.rs | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 tests/ui/unnecessary_sort_by_no_core.rs diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index c8cb85c05156..9dddbe814317 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -190,6 +190,12 @@ pub(super) fn check<'tcx>( } else { "sort_by_key" }; + let Some(std_or_core) = std_or_core(cx) else { + // To make it this far the crate has to reference diagnostic items defined in core. Either this is + // the `core` crate, there's an `extern crate core` somewhere, or another crate is defining the + // diagnostic items. It's fine to not lint in all those cases even if we might be able to. + return; + }; span_lint_and_then( cx, UNNECESSARY_SORT_BY, @@ -203,20 +209,15 @@ pub(super) fn check<'tcx>( }; let recv = Sugg::hir_with_applicability(cx, recv, "(_)", &mut app); let closure_body = Sugg::hir_with_applicability(cx, trigger.closure_body, "_", &mut app); + let closure_body = if trigger.reverse { + format!("{std_or_core}::cmp::Reverse({closure_body})") + } else { + closure_body.to_string() + }; diag.span_suggestion( expr.span, "try", - format!( - "{recv}.{method}(|{}| {})", - trigger.closure_arg, - if let Some(std_or_core) = std_or_core(cx) - && trigger.reverse - { - format!("{std_or_core}::cmp::Reverse({closure_body})") - } else { - closure_body.to_string() - }, - ), + format!("{recv}.{method}(|{}| {})", trigger.closure_arg, closure_body), app, ); }, diff --git a/tests/ui/unnecessary_sort_by_no_core.rs b/tests/ui/unnecessary_sort_by_no_core.rs new file mode 100644 index 000000000000..cb0da6339cb3 --- /dev/null +++ b/tests/ui/unnecessary_sort_by_no_core.rs @@ -0,0 +1,26 @@ +//@check-pass +#![feature(no_core)] +#![no_std] +#![no_core] +extern crate alloc; +extern crate core as mycore; +use alloc::vec; +use alloc::vec::Vec; +use mycore::cmp::Ord as _; + +fn issue_11524() -> Vec { + let mut vec = vec![1, 2, 3]; + + // We could lint and suggest `vec.sort_by_key(|a| a + 1);`, but we don't bother to -- see the + // comment in the lint at line 194 + vec.sort_by(|a, b| (a + 1).cmp(&(b + 1))); + vec +} + +fn issue_11524_2() -> Vec { + let mut vec = vec![1, 2, 3]; + + // Should not lint, as even `vec.sort_by_key(|b| core::cmp::Reverse(b + 1));` would not compile + vec.sort_by(|a, b| (b + 1).cmp(&(a + 1))); + vec +} From d0d725133fb501ffb9f4572d2ea59454e68bb75a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 8 Jan 2026 21:08:15 +0100 Subject: [PATCH 035/273] clean-up --- clippy_lints/src/floating_point_arithmetic.rs | 202 +++++++++--------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 5f022ba307ff..0a8f6c6ebf6b 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -1,6 +1,6 @@ use clippy_utils::consts::Constant::{F32, F64, Int}; use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::{ eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, is_no_std_crate, @@ -128,16 +128,16 @@ fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: Synta // Adds type suffixes and parenthesis to method receivers if necessary fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> { - let mut suggestion = Sugg::hir(cx, expr, ".."); + let mut suggestion = Sugg::hir(cx, expr, "_"); - if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { + if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind { expr = inner_expr; } if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind() // if the expression is a float literal and it is unsuffixed then // add a suffix so the suggestion is valid and unambiguous - && let ExprKind::Lit(lit) = &expr.kind + && let ExprKind::Lit(lit) = expr.kind && let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node { let op = format!( @@ -166,7 +166,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, ar expr.span, "logarithm for bases 2, 10 and e can be computed more accurately", "consider using", - format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_paren()), + format!("{}.{method}()", Sugg::hir(cx, receiver, "_").maybe_paren()), Applicability::MachineApplicable, ); } @@ -251,25 +251,25 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: // Check argument if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { + let recv = Sugg::hir(cx, receiver, "_").maybe_paren(); let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { ( SUBOPTIMAL_FLOPS, "square-root of a number can be computed more efficiently and accurately", - format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_paren()), + format!("{recv}.sqrt()"), ) } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { ( IMPRECISE_FLOPS, "cube-root of a number can be computed more accurately", - format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_paren()), + format!("{recv}.cbrt()"), ) } else if let Some(exponent) = get_integer_from_float_constant(&value) { ( SUBOPTIMAL_FLOPS, "exponentiation with integer powers can be computed more efficiently", format!( - "{}.powi({})", - Sugg::hir(cx, receiver, "..").maybe_paren(), + "{recv}.powi({})", numeric_literal::format(&exponent.to_string(), None, false) ), ) @@ -311,31 +311,36 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: rhs, ) = parent.kind { - let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; - - // Negate expr if original code has subtraction and expr is on the right side - let maybe_neg_sugg = |expr, hir_id| { - let sugg = Sugg::hir(cx, expr, ".."); - if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { - -sugg - } else { - sugg - } - }; - - span_lint_and_sugg( + span_lint_and_then( cx, SUBOPTIMAL_FLOPS, parent.span, "multiply and add expressions can be calculated more efficiently and accurately", - "consider using", - format!( - "{}.mul_add({}, {})", - Sugg::hir(cx, receiver, "..").maybe_paren(), - maybe_neg_sugg(receiver, expr.hir_id), - maybe_neg_sugg(other_addend, other_addend.hir_id), - ), - Applicability::MachineApplicable, + |diag| { + let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; + + // Negate expr if original code has subtraction and expr is on the right side + let maybe_neg_sugg = |expr, hir_id| { + let sugg = Sugg::hir(cx, expr, "_"); + if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { + -sugg + } else { + sugg + } + }; + + diag.span_suggestion( + parent.span, + "consider using", + format!( + "{}.mul_add({}, {})", + Sugg::hir(cx, receiver, "_").maybe_paren(), + maybe_neg_sugg(receiver, expr.hir_id), + maybe_neg_sugg(other_addend, other_addend.hir_id), + ), + Applicability::MachineApplicable, + ); + }, ); } } @@ -370,14 +375,14 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { { return Some(format!( "{}.hypot({})", - Sugg::hir(cx, lmul_lhs, "..").maybe_paren(), - Sugg::hir(cx, rmul_lhs, "..") + Sugg::hir(cx, lmul_lhs, "_").maybe_paren(), + Sugg::hir(cx, rmul_lhs, "_") )); } // check if expression of the form x.powi(2) + y.powi(2) - if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = &add_lhs.kind - && let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = &add_rhs.kind + if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = add_lhs.kind + && let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = add_rhs.kind && lmethod.name == sym::powi && rmethod.name == sym::powi && let ecx = ConstEvalCtxt::new(cx) @@ -388,8 +393,8 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { { return Some(format!( "{}.hypot({})", - Sugg::hir(cx, largs_0, "..").maybe_paren(), - Sugg::hir(cx, rargs_0, "..") + Sugg::hir(cx, largs_0, "_").maybe_paren(), + Sugg::hir(cx, rargs_0, "_") )); } } @@ -421,7 +426,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { lhs, rhs, ) = expr.kind - && let ExprKind::MethodCall(path, self_arg, [], _) = &lhs.kind + && let ExprKind::MethodCall(path, self_arg, [], _) = lhs.kind && path.ident.name == sym::exp && cx.typeck_results().expr_ty(lhs).is_floating_point() && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) @@ -434,7 +439,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "(e.pow(x) - 1) can be computed more accurately", "consider using", - format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_paren()), + format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "_").maybe_paren()), Applicability::MachineApplicable, ); } @@ -447,7 +452,7 @@ fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&' }, lhs, rhs, - ) = &expr.kind + ) = expr.kind && cx.typeck_results().expr_ty(lhs).is_floating_point() && cx.typeck_results().expr_ty(rhs).is_floating_point() { @@ -476,18 +481,18 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { } let maybe_neg_sugg = |expr| { - let sugg = Sugg::hir(cx, expr, ".."); + let sugg = Sugg::hir(cx, expr, "_"); if let BinOpKind::Sub = op { -sugg } else { sugg } }; let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) && cx.typeck_results().expr_ty(rhs).is_floating_point() { - (inner_lhs, Sugg::hir(cx, inner_rhs, ".."), maybe_neg_sugg(rhs)) + (inner_lhs, Sugg::hir(cx, inner_rhs, "_"), maybe_neg_sugg(rhs)) } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) && cx.typeck_results().expr_ty(lhs).is_floating_point() { - (inner_lhs, maybe_neg_sugg(inner_rhs), Sugg::hir(cx, lhs, "..")) + (inner_lhs, maybe_neg_sugg(inner_rhs), Sugg::hir(cx, lhs, "_")) } else { return; }; @@ -558,12 +563,12 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { /// If the two expressions are not negations of each other, then it /// returns None. fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { - if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind + if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind && eq_expr_value(cx, expr1_negated, expr2) { return Some((false, expr2)); } - if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind + if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind && eq_expr_value(cx, expr1, expr2_negated) { return Some((true, expr1)); @@ -581,29 +586,20 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { && let else_body_expr = peel_blocks(r#else) && let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr) { - let positive_abs_sugg = ( - "manual implementation of `abs` method", - format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()), - ); - let negative_abs_sugg = ( - "manual implementation of negation of `abs` method", - format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()), - ); - let sugg = if is_testing_positive(cx, cond, body) { - if if_expr_positive { - positive_abs_sugg - } else { - negative_abs_sugg - } + let sugg_positive_abs = if is_testing_positive(cx, cond, body) { + if_expr_positive } else if is_testing_negative(cx, cond, body) { - if if_expr_positive { - negative_abs_sugg - } else { - positive_abs_sugg - } + !if_expr_positive } else { return; }; + let body = Sugg::hir(cx, body, "_").maybe_paren(); + let sugg = if sugg_positive_abs { + ("manual implementation of `abs` method", format!("{body}.abs()")) + } else { + #[rustfmt::skip] + ("manual implementation of negation of `abs` method", format!("-{body}.abs()")) + }; span_lint_and_sugg( cx, SUBOPTIMAL_FLOPS, @@ -637,10 +633,10 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { }, lhs, rhs, - ) = &expr.kind + ) = expr.kind && are_same_base_logs(cx, lhs, rhs) - && let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind - && let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind + && let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind + && let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind { span_lint_and_sugg( cx, @@ -650,8 +646,8 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { "consider using", format!( "{}.log({})", - Sugg::hir(cx, largs_self, "..").maybe_paren(), - Sugg::hir(cx, rargs_self, ".."), + Sugg::hir(cx, largs_self, "_").maybe_paren(), + Sugg::hir(cx, rargs_self, "_"), ), Applicability::MachineApplicable, ); @@ -665,14 +661,14 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { }, div_lhs, div_rhs, - ) = &expr.kind + ) = expr.kind && let ExprKind::Binary( Spanned { node: BinOpKind::Mul, .. }, mul_lhs, mul_rhs, - ) = &div_lhs.kind + ) = div_lhs.kind && let ecx = ConstEvalCtxt::new(cx) && let Some(rvalue) = ecx.eval(div_rhs) && let Some(lvalue) = ecx.eval(mul_rhs) @@ -681,48 +677,52 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) { - let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_paren()); - if let ExprKind::Lit(literal) = mul_lhs.kind - && let ast::LitKind::Float(ref value, float_type) = literal.node - && float_type == ast::LitFloatType::Unsuffixed - { - if value.as_str().ends_with('.') { - proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, "..")); - } else { - proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, "..")); - } - } - span_lint_and_sugg( + span_lint_and_then( cx, SUBOPTIMAL_FLOPS, expr.span, "conversion to degrees can be done more accurately", - "consider using", - proposal, - Applicability::MachineApplicable, + |diag| { + let recv = Sugg::hir(cx, mul_lhs, "num"); + let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind + && let ast::LitKind::Float(ref value, float_type) = literal.node + && float_type == ast::LitFloatType::Unsuffixed + { + if value.as_str().ends_with('.') { + format!("{recv}0_f64.to_degrees()") + } else { + format!("{recv}_f64.to_degrees()") + } + } else { + format!("{}.to_degrees()", recv.maybe_paren()) + }; + diag.span_suggestion(expr.span, "consider using", proposal, Applicability::MachineApplicable); + }, ); } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) { - let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_paren()); - if let ExprKind::Lit(literal) = mul_lhs.kind - && let ast::LitKind::Float(ref value, float_type) = literal.node - && float_type == ast::LitFloatType::Unsuffixed - { - if value.as_str().ends_with('.') { - proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, "..")); - } else { - proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, "..")); - } - } - span_lint_and_sugg( + span_lint_and_then( cx, SUBOPTIMAL_FLOPS, expr.span, "conversion to radians can be done more accurately", - "consider using", - proposal, - Applicability::MachineApplicable, + |diag| { + let recv = Sugg::hir(cx, mul_lhs, "num"); + let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind + && let ast::LitKind::Float(ref value, float_type) = literal.node + && float_type == ast::LitFloatType::Unsuffixed + { + if value.as_str().ends_with('.') { + format!("{recv}0_f64.to_radians()") + } else { + format!("{recv}_f64.to_radians()") + } + } else { + format!("{}.to_radians()", recv.maybe_paren()) + }; + diag.span_suggestion(expr.span, "consider using", proposal, Applicability::MachineApplicable); + }, ); } } @@ -735,7 +735,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { return; } - if let ExprKind::MethodCall(path, receiver, args, _) = &expr.kind { + if let ExprKind::MethodCall(path, receiver, args, _) = expr.kind { let recv_ty = cx.typeck_results().expr_ty(receiver); if recv_ty.is_floating_point() && !is_no_std_crate(cx) && cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) { From eb101b1d2a2b7ee1407935ba8c5bee36ac9ee31d Mon Sep 17 00:00:00 2001 From: Paul Mabileau Date: Thu, 18 Dec 2025 12:55:04 +0100 Subject: [PATCH 036/273] Fix(lib/win/thread): Ensure `Sleep`'s usage passes over the requested duration under Win7 Fixes #149935. See the added comment for more details. Signed-off-by: Paul Mabileau --- library/std/src/sys/thread/windows.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/library/std/src/sys/thread/windows.rs b/library/std/src/sys/thread/windows.rs index 6a21b11e0312..ea18572489ee 100644 --- a/library/std/src/sys/thread/windows.rs +++ b/library/std/src/sys/thread/windows.rs @@ -8,7 +8,7 @@ use crate::sys::pal::time::WaitableTimer; use crate::sys::pal::{dur2timeout, to_u16s}; use crate::sys::{FromInner, c, stack_overflow}; use crate::thread::ThreadInit; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{io, ptr}; pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024; @@ -120,11 +120,28 @@ pub fn sleep(dur: Duration) { timer.set(dur)?; timer.wait() } + // Directly forward to `Sleep` for its zero duration behavior when indeed + // zero in order to skip the `Instant::now` calls, useless in this case. + if dur.is_zero() { + unsafe { c::Sleep(0) }; // Attempt to use high-precision sleep (Windows 10, version 1803+). - // On error fallback to the standard `Sleep` function. - // Also preserves the zero duration behavior of `Sleep`. - if dur.is_zero() || high_precision_sleep(dur).is_err() { - unsafe { c::Sleep(dur2timeout(dur)) } + // On error, fallback to the standard `Sleep` function. + } else if high_precision_sleep(dur).is_err() { + let start = Instant::now(); + unsafe { c::Sleep(dur2timeout(dur)) }; + + // See #149935: `Sleep` under Windows 7 and probably 8 as well seems a + // bit buggy for us as it can last less than the requested time while + // our API is meant to guarantee that. This is fixed by measuring the + // effective time difference and if needed, sleeping a bit more in + // order to ensure the duration is always exceeded. A fixed single + // millisecond works because `Sleep` operates based on a system-wide + // (until Windows 10 2004 that makes it process-local) interrupt timer + // that counts in "tick" units of ~15ms by default: a 1ms timeout + // therefore passes the next tick boundary. + if start.elapsed() < dur { + unsafe { c::Sleep(1) }; + } } } From cb9a079f608e6fed49a9303ff68ecca322e5b06b Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 8 Jan 2026 21:24:50 +0100 Subject: [PATCH 037/273] fix(float_point_arithmetic): respect reduced applicability --- clippy_lints/src/floating_point_arithmetic.rs | 142 ++++++++++-------- 1 file changed, 76 insertions(+), 66 deletions(-) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 0a8f6c6ebf6b..64a2af38a108 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -127,8 +127,8 @@ fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: Synta } // Adds type suffixes and parenthesis to method receivers if necessary -fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> { - let mut suggestion = Sugg::hir(cx, expr, "_"); +fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>, app: &mut Applicability) -> Sugg<'a> { + let mut suggestion = Sugg::hir_with_applicability(cx, expr, "_", app); if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind { expr = inner_expr; @@ -160,14 +160,16 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { - span_lint_and_sugg( + span_lint_and_then( cx, SUBOPTIMAL_FLOPS, expr.span, "logarithm for bases 2, 10 and e can be computed more accurately", - "consider using", - format!("{}.{method}()", Sugg::hir(cx, receiver, "_").maybe_paren()), - Applicability::MachineApplicable, + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); + }, ); } } @@ -190,14 +192,16 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { _ => return, }; - span_lint_and_sugg( + span_lint_and_then( cx, IMPRECISE_FLOPS, expr.span, "ln(1 + x) can be computed more accurately", - "consider using", - format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)), - Applicability::MachineApplicable, + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = prepare_receiver_sugg(cx, recv, &mut app); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.ln_1p()"), app); + }, ); } } @@ -238,20 +242,23 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: None } { - span_lint_and_sugg( + span_lint_and_then( cx, SUBOPTIMAL_FLOPS, expr.span, "exponent for bases 2 and e can be computed more accurately", - "consider using", - format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])), - Applicability::MachineApplicable, + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = prepare_receiver_sugg(cx, &args[0], &mut app); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); + }, ); } // Check argument if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { - let recv = Sugg::hir(cx, receiver, "_").maybe_paren(); + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { ( SUBOPTIMAL_FLOPS, @@ -277,15 +284,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: return; }; - span_lint_and_sugg( - cx, - lint, - expr.span, - help, - "consider using", - suggestion, - Applicability::MachineApplicable, - ); + span_lint_and_sugg(cx, lint, expr.span, help, "consider using", suggestion, app); } } @@ -297,7 +296,8 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: if let Some(grandparent) = get_parent_expr(cx, parent) && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind && method.name == sym::sqrt - && detect_hypot(cx, receiver).is_some() + // we don't care about the applicability as this is an early-return condition + && detect_hypot(cx, receiver, &mut Applicability::Unspecified).is_some() { return; } @@ -320,8 +320,8 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; // Negate expr if original code has subtraction and expr is on the right side - let maybe_neg_sugg = |expr, hir_id| { - let sugg = Sugg::hir(cx, expr, "_"); + let maybe_neg_sugg = |expr, hir_id, app: &mut _| { + let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { -sugg } else { @@ -329,16 +329,17 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } }; + let mut app = Applicability::MachineApplicable; diag.span_suggestion( parent.span, "consider using", format!( "{}.mul_add({}, {})", - Sugg::hir(cx, receiver, "_").maybe_paren(), - maybe_neg_sugg(receiver, expr.hir_id), - maybe_neg_sugg(other_addend, other_addend.hir_id), + Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(), + maybe_neg_sugg(receiver, expr.hir_id, &mut app), + maybe_neg_sugg(other_addend, other_addend.hir_id, &mut app), ), - Applicability::MachineApplicable, + app, ); }, ); @@ -346,7 +347,7 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } } -fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { +fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applicability) -> Option { if let ExprKind::Binary( Spanned { node: BinOpKind::Add, .. @@ -375,8 +376,8 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { { return Some(format!( "{}.hypot({})", - Sugg::hir(cx, lmul_lhs, "_").maybe_paren(), - Sugg::hir(cx, rmul_lhs, "_") + Sugg::hir_with_applicability(cx, lmul_lhs, "_", app).maybe_paren(), + Sugg::hir_with_applicability(cx, rmul_lhs, "_", app) )); } @@ -393,8 +394,8 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { { return Some(format!( "{}.hypot({})", - Sugg::hir(cx, largs_0, "_").maybe_paren(), - Sugg::hir(cx, rargs_0, "_") + Sugg::hir_with_applicability(cx, largs_0, "_", app).maybe_paren(), + Sugg::hir_with_applicability(cx, rargs_0, "_", app) )); } } @@ -403,7 +404,8 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option { } fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { - if let Some(message) = detect_hypot(cx, receiver) { + let mut app = Applicability::MachineApplicable; + if let Some(message) = detect_hypot(cx, receiver, &mut app) { span_lint_and_sugg( cx, IMPRECISE_FLOPS, @@ -411,7 +413,7 @@ fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { "hypotenuse can be computed more accurately", "consider using", message, - Applicability::MachineApplicable, + app, ); } } @@ -433,14 +435,16 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { && (F32(1.0) == value || F64(1.0) == value) && cx.typeck_results().expr_ty(self_arg).is_floating_point() { - span_lint_and_sugg( + span_lint_and_then( cx, IMPRECISE_FLOPS, expr.span, "(e.pow(x) - 1) can be computed more accurately", - "consider using", - format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "_").maybe_paren()), - Applicability::MachineApplicable, + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, self_arg, "_", &mut app).maybe_paren(); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.exp_m1()"), app); + }, ); } } @@ -475,24 +479,34 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { if let Some(parent) = get_parent_expr(cx, expr) && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind && method.name == sym::sqrt - && detect_hypot(cx, receiver).is_some() + // we don't care about the applicability as this is an early-return condition + && detect_hypot(cx, receiver, &mut Applicability::Unspecified).is_some() { return; } - let maybe_neg_sugg = |expr| { - let sugg = Sugg::hir(cx, expr, "_"); + let maybe_neg_sugg = |expr, app: &mut _| { + let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); if let BinOpKind::Sub = op { -sugg } else { sugg } }; + let mut app = Applicability::MachineApplicable; let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) && cx.typeck_results().expr_ty(rhs).is_floating_point() { - (inner_lhs, Sugg::hir(cx, inner_rhs, "_"), maybe_neg_sugg(rhs)) + ( + inner_lhs, + Sugg::hir_with_applicability(cx, inner_rhs, "_", &mut app), + maybe_neg_sugg(rhs, &mut app), + ) } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) && cx.typeck_results().expr_ty(lhs).is_floating_point() { - (inner_lhs, maybe_neg_sugg(inner_rhs), Sugg::hir(cx, lhs, "_")) + ( + inner_lhs, + maybe_neg_sugg(inner_rhs, &mut app), + Sugg::hir_with_applicability(cx, lhs, "_", &mut app), + ) } else { return; }; @@ -511,8 +525,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "multiply and add expressions can be calculated more efficiently and accurately", "consider using", - format!("{}.mul_add({arg1}, {arg2})", prepare_receiver_sugg(cx, recv)), - Applicability::MachineApplicable, + format!("{}.mul_add({arg1}, {arg2})", prepare_receiver_sugg(cx, recv, &mut app)), + app, ); } } @@ -593,22 +607,15 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { } else { return; }; - let body = Sugg::hir(cx, body, "_").maybe_paren(); + let mut app = Applicability::MachineApplicable; + let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren(); let sugg = if sugg_positive_abs { ("manual implementation of `abs` method", format!("{body}.abs()")) } else { #[rustfmt::skip] ("manual implementation of negation of `abs` method", format!("-{body}.abs()")) }; - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - sugg.0, - "try", - sugg.1, - Applicability::MachineApplicable, - ); + span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app); } } @@ -638,6 +645,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { && let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind && let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind { + let mut app = Applicability::MachineApplicable; span_lint_and_sugg( cx, SUBOPTIMAL_FLOPS, @@ -646,10 +654,10 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { "consider using", format!( "{}.log({})", - Sugg::hir(cx, largs_self, "_").maybe_paren(), - Sugg::hir(cx, rargs_self, "_"), + Sugg::hir_with_applicability(cx, largs_self, "_", &mut app).maybe_paren(), + Sugg::hir_with_applicability(cx, rargs_self, "_", &mut app), ), - Applicability::MachineApplicable, + app, ); } } @@ -683,7 +691,8 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "conversion to degrees can be done more accurately", |diag| { - let recv = Sugg::hir(cx, mul_lhs, "num"); + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind && let ast::LitKind::Float(ref value, float_type) = literal.node && float_type == ast::LitFloatType::Unsuffixed @@ -696,7 +705,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { } else { format!("{}.to_degrees()", recv.maybe_paren()) }; - diag.span_suggestion(expr.span, "consider using", proposal, Applicability::MachineApplicable); + diag.span_suggestion(expr.span, "consider using", proposal, app); }, ); } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) @@ -708,7 +717,8 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "conversion to radians can be done more accurately", |diag| { - let recv = Sugg::hir(cx, mul_lhs, "num"); + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind && let ast::LitKind::Float(ref value, float_type) = literal.node && float_type == ast::LitFloatType::Unsuffixed @@ -721,7 +731,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { } else { format!("{}.to_radians()", recv.maybe_paren()) }; - diag.span_suggestion(expr.span, "consider using", proposal, Applicability::MachineApplicable); + diag.span_suggestion(expr.span, "consider using", proposal, app); }, ); } From 7a377d66727e441c8472c18cb7aab3a597be1639 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 8 Jan 2026 22:03:26 +0100 Subject: [PATCH 038/273] move `floating_point_arithmetic.rs` to `floating_point_arithmetic/mod.rs` --- .../mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clippy_lints/src/{floating_point_arithmetic.rs => floating_point_arithmetic/mod.rs} (100%) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic/mod.rs similarity index 100% rename from clippy_lints/src/floating_point_arithmetic.rs rename to clippy_lints/src/floating_point_arithmetic/mod.rs From 44a41041fcb00f6fd1f76beeef53db936d39e31d Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 8 Jan 2026 22:46:58 +0100 Subject: [PATCH 039/273] extract each check into a separate module --- .../floating_point_arithmetic/custom_abs.rs | 100 +++ .../src/floating_point_arithmetic/expm1.rs | 42 ++ .../src/floating_point_arithmetic/hypot.rs | 82 +++ .../src/floating_point_arithmetic/lib.rs | 42 ++ .../src/floating_point_arithmetic/ln1p.rs | 41 ++ .../src/floating_point_arithmetic/log_base.rs | 44 ++ .../floating_point_arithmetic/log_division.rs | 52 ++ .../src/floating_point_arithmetic/mod.rs | 678 +----------------- .../src/floating_point_arithmetic/mul_add.rs | 94 +++ .../src/floating_point_arithmetic/powf.rs | 94 +++ .../src/floating_point_arithmetic/powi.rs | 70 ++ .../src/floating_point_arithmetic/radians.rs | 89 +++ 12 files changed, 774 insertions(+), 654 deletions(-) create mode 100644 clippy_lints/src/floating_point_arithmetic/custom_abs.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/expm1.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/hypot.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/lib.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/ln1p.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/log_base.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/log_division.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/mul_add.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/powf.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/powi.rs create mode 100644 clippy_lints/src/floating_point_arithmetic/radians.rs diff --git a/clippy_lints/src/floating_point_arithmetic/custom_abs.rs b/clippy_lints/src/floating_point_arithmetic/custom_abs.rs new file mode 100644 index 000000000000..d12a32e15881 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/custom_abs.rs @@ -0,0 +1,100 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64, Int}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eq_expr_value, higher, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_span::SyntaxContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +/// Returns true iff expr is an expression which tests whether or not +/// test is positive or an expression which tests whether or not test +/// is nonnegative. +/// Used for check-custom-abs function below +fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { + if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { + match op { + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), + _ => false, + } + } else { + false + } +} + +/// See [`is_testing_positive`] +fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { + if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { + match op { + BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), + BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), + _ => false, + } + } else { + false + } +} + +/// Returns true iff expr is some zero literal +fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { + match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { + Some(Int(i)) => i == 0, + Some(F32(f)) => f == 0.0, + Some(F64(f)) => f == 0.0, + _ => false, + } +} + +/// If the two expressions are negations of each other, then it returns +/// a tuple, in which the first element is true iff expr1 is the +/// positive expressions, and the second element is the positive +/// one of the two expressions +/// If the two expressions are not negations of each other, then it +/// returns None. +fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { + if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind + && eq_expr_value(cx, expr1_negated, expr2) + { + return Some((false, expr2)); + } + if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind + && eq_expr_value(cx, expr1, expr2_negated) + { + return Some((true, expr1)); + } + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some(higher::If { + cond, + then, + r#else: Some(r#else), + }) = higher::If::hir(expr) + && let if_body_expr = peel_blocks(then) + && let else_body_expr = peel_blocks(r#else) + && let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr) + { + let sugg_positive_abs = if is_testing_positive(cx, cond, body) { + if_expr_positive + } else if is_testing_negative(cx, cond, body) { + !if_expr_positive + } else { + return; + }; + let mut app = Applicability::MachineApplicable; + let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren(); + let sugg = if sugg_positive_abs { + ("manual implementation of `abs` method", format!("{body}.abs()")) + } else { + #[rustfmt::skip] + ("manual implementation of negation of `abs` method", format!("-{body}.abs()")) + }; + span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/expm1.rs b/clippy_lints/src/floating_point_arithmetic/expm1.rs new file mode 100644 index 000000000000..9a4c97569308 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/expm1.rs @@ -0,0 +1,42 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::IMPRECISE_FLOPS; + +// TODO: Lint expressions of the form `x.exp() - y` where y > 1 +// and suggest usage of `x.exp_m1() - (y - 1)` instead +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, .. + }, + lhs, + rhs, + ) = expr.kind + && let ExprKind::MethodCall(path, self_arg, [], _) = lhs.kind + && path.ident.name == sym::exp + && cx.typeck_results().expr_ty(lhs).is_floating_point() + && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) + && (F32(1.0) == value || F64(1.0) == value) + && cx.typeck_results().expr_ty(self_arg).is_floating_point() + { + span_lint_and_then( + cx, + IMPRECISE_FLOPS, + expr.span, + "(e.pow(x) - 1) can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, self_arg, "_", &mut app).maybe_paren(); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.exp_m1()"), app); + }, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/hypot.rs b/clippy_lints/src/floating_point_arithmetic/hypot.rs new file mode 100644 index 000000000000..49f8ba4bf825 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/hypot.rs @@ -0,0 +1,82 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::Int; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eq_expr_value, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::IMPRECISE_FLOPS; + +pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applicability) -> Option { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + add_lhs, + add_rhs, + ) = receiver.kind + { + // check if expression of the form x * x + y * y + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + lmul_lhs, + lmul_rhs, + ) = add_lhs.kind + && let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + rmul_lhs, + rmul_rhs, + ) = add_rhs.kind + && eq_expr_value(cx, lmul_lhs, lmul_rhs) + && eq_expr_value(cx, rmul_lhs, rmul_rhs) + { + return Some(format!( + "{}.hypot({})", + Sugg::hir_with_applicability(cx, lmul_lhs, "_", app).maybe_paren(), + Sugg::hir_with_applicability(cx, rmul_lhs, "_", app) + )); + } + + // check if expression of the form x.powi(2) + y.powi(2) + if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = add_lhs.kind + && let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = add_rhs.kind + && lmethod.name == sym::powi + && rmethod.name == sym::powi + && let ecx = ConstEvalCtxt::new(cx) + && let Some(lvalue) = ecx.eval(largs_1) + && let Some(rvalue) = ecx.eval(rargs_1) + && Int(2) == lvalue + && Int(2) == rvalue + { + return Some(format!( + "{}.hypot({})", + Sugg::hir_with_applicability(cx, largs_0, "_", app).maybe_paren(), + Sugg::hir_with_applicability(cx, rargs_0, "_", app) + )); + } + } + + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { + let mut app = Applicability::MachineApplicable; + if let Some(message) = detect(cx, receiver, &mut app) { + span_lint_and_sugg( + cx, + IMPRECISE_FLOPS, + expr.span, + "hypotenuse can be computed more accurately", + "consider using", + message, + app, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/lib.rs b/clippy_lints/src/floating_point_arithmetic/lib.rs new file mode 100644 index 000000000000..3fa041f97802 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/lib.rs @@ -0,0 +1,42 @@ +use clippy_utils::sugg::Sugg; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +// Adds type suffixes and parenthesis to method receivers if necessary +pub(super) fn prepare_receiver_sugg<'a>( + cx: &LateContext<'_>, + mut expr: &'a Expr<'a>, + app: &mut Applicability, +) -> Sugg<'a> { + let mut suggestion = Sugg::hir_with_applicability(cx, expr, "_", app); + + if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind { + expr = inner_expr; + } + + if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind() + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + && let ExprKind::Lit(lit) = expr.kind + && let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node + { + let op = format!( + "{suggestion}{}{}", + // Check for float literals without numbers following the decimal + // separator such as `2.` and adds a trailing zero + if sym.as_str().ends_with('.') { "0" } else { "" }, + float_ty.name_str() + ) + .into(); + + suggestion = match suggestion { + Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op), + _ => Sugg::NonParen(op), + }; + } + + suggestion.maybe_paren() +} diff --git a/clippy_lints/src/floating_point_arithmetic/ln1p.rs b/clippy_lints/src/floating_point_arithmetic/ln1p.rs new file mode 100644 index 000000000000..4c9aa96b5042 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/ln1p.rs @@ -0,0 +1,41 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::IMPRECISE_FLOPS; + +// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and +// suggest usage of `(x + (y - 1)).ln_1p()` instead +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Add, .. + }, + lhs, + rhs, + ) = receiver.kind + { + let ecx = ConstEvalCtxt::new(cx); + let recv = match (ecx.eval(lhs), ecx.eval(rhs)) { + (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, + (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, + _ => return, + }; + + span_lint_and_then( + cx, + IMPRECISE_FLOPS, + expr.span, + "ln(1 + x) can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = super::lib::prepare_receiver_sugg(cx, recv, &mut app); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.ln_1p()"), app); + }, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/log_base.rs b/clippy_lints/src/floating_point_arithmetic/log_base.rs new file mode 100644 index 000000000000..4ccc784655ed --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/log_base.rs @@ -0,0 +1,44 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::SyntaxContext; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; + +use super::SUBOPTIMAL_FLOPS; + +// Returns the specialized log method for a given base if base is constant +// and is one of 2, 10 and e +fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> { + if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) { + if F32(2.0) == value || F64(2.0) == value { + return Some("log2"); + } else if F32(10.0) == value || F64(10.0) == value { + return Some("log10"); + } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + return Some("ln"); + } + } + + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { + if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "logarithm for bases 2, 10 and e can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); + }, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/log_division.rs b/clippy_lints/src/floating_point_arithmetic/log_division.rs new file mode 100644 index 000000000000..e3419ffad72a --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/log_division.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eq_expr_value, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { + if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind + && let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind + { + return method_a.name == method_b.name + && args_a.len() == args_b.len() + && (matches!(method_a.name, sym::ln | sym::log2 | sym::log10) + || method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])); + } + + false +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + // check if expression of the form x.logN() / y.logN() + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Div, .. + }, + lhs, + rhs, + ) = expr.kind + && are_same_base_logs(cx, lhs, rhs) + && let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind + && let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind + { + let mut app = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "log base can be expressed more clearly", + "consider using", + format!( + "{}.log({})", + Sugg::hir_with_applicability(cx, largs_self, "_", &mut app).maybe_paren(), + Sugg::hir_with_applicability(cx, rargs_self, "_", &mut app), + ), + app, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/mod.rs b/clippy_lints/src/floating_point_arithmetic/mod.rs index 64a2af38a108..edc638c96bbf 100644 --- a/clippy_lints/src/floating_point_arithmetic/mod.rs +++ b/clippy_lints/src/floating_point_arithmetic/mod.rs @@ -1,22 +1,20 @@ -use clippy_utils::consts::Constant::{F32, F64, Int}; -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::{ - eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, is_no_std_crate, - numeric_literal, peel_blocks, sugg, sym, -}; -use rustc_ast::ast; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; +use clippy_utils::{is_in_const_context, is_no_std_crate, sym}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::SyntaxContext; -use rustc_span::source_map::Spanned; -use std::f32::consts as f32_consts; -use std::f64::consts as f64_consts; -use sugg::Sugg; + +mod custom_abs; +mod expm1; +mod hypot; +mod lib; +mod ln1p; +mod log_base; +mod log_division; +mod mul_add; +mod powf; +mod powi; +mod radians; declare_clippy_lint! { /// ### What it does @@ -110,634 +108,6 @@ declare_lint_pass!(FloatingPointArithmetic => [ SUBOPTIMAL_FLOPS ]); -// Returns the specialized log method for a given base if base is constant -// and is one of 2, 10 and e -fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> { - if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) { - if F32(2.0) == value || F64(2.0) == value { - return Some("log2"); - } else if F32(10.0) == value || F64(10.0) == value { - return Some("log10"); - } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value { - return Some("ln"); - } - } - - None -} - -// Adds type suffixes and parenthesis to method receivers if necessary -fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>, app: &mut Applicability) -> Sugg<'a> { - let mut suggestion = Sugg::hir_with_applicability(cx, expr, "_", app); - - if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind { - expr = inner_expr; - } - - if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind() - // if the expression is a float literal and it is unsuffixed then - // add a suffix so the suggestion is valid and unambiguous - && let ExprKind::Lit(lit) = expr.kind - && let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node - { - let op = format!( - "{suggestion}{}{}", - // Check for float literals without numbers following the decimal - // separator such as `2.` and adds a trailing zero - if sym.as_str().ends_with('.') { "0" } else { "" }, - float_ty.name_str() - ) - .into(); - - suggestion = match suggestion { - Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op), - _ => Sugg::NonParen(op), - }; - } - - suggestion.maybe_paren() -} - -fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "logarithm for bases 2, 10 and e can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); - }, - ); - } -} - -// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and -// suggest usage of `(x + (y - 1)).ln_1p()` instead -fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Add, .. - }, - lhs, - rhs, - ) = receiver.kind - { - let ecx = ConstEvalCtxt::new(cx); - let recv = match (ecx.eval(lhs), ecx.eval(rhs)) { - (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, - (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, - _ => return, - }; - - span_lint_and_then( - cx, - IMPRECISE_FLOPS, - expr.span, - "ln(1 + x) can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = prepare_receiver_sugg(cx, recv, &mut app); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.ln_1p()"), app); - }, - ); - } -} - -// Returns an integer if the float constant is a whole number and it can be -// converted to an integer without loss of precision. For now we only check -// ranges [-16777215, 16777216) for type f32 as whole number floats outside -// this range are lossy and ambiguous. -#[expect(clippy::cast_possible_truncation)] -fn get_integer_from_float_constant(value: &Constant) -> Option { - match value { - F32(num) if num.fract() == 0.0 => { - if (-16_777_215.0..16_777_216.0).contains(num) { - Some(num.round() as i32) - } else { - None - } - }, - F64(num) if num.fract() == 0.0 => { - if (-2_147_483_648.0..2_147_483_648.0).contains(num) { - Some(num.round() as i32) - } else { - None - } - }, - _ => None, - } -} - -fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - // Check receiver - if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) - && let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { - Some("exp") - } else if F32(2.0) == value || F64(2.0) == value { - Some("exp2") - } else { - None - } - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "exponent for bases 2 and e can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = prepare_receiver_sugg(cx, &args[0], &mut app); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); - }, - ); - } - - // Check argument - if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); - let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { - ( - SUBOPTIMAL_FLOPS, - "square-root of a number can be computed more efficiently and accurately", - format!("{recv}.sqrt()"), - ) - } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { - ( - IMPRECISE_FLOPS, - "cube-root of a number can be computed more accurately", - format!("{recv}.cbrt()"), - ) - } else if let Some(exponent) = get_integer_from_float_constant(&value) { - ( - SUBOPTIMAL_FLOPS, - "exponentiation with integer powers can be computed more efficiently", - format!( - "{recv}.powi({})", - numeric_literal::format(&exponent.to_string(), None, false) - ), - ) - } else { - return; - }; - - span_lint_and_sugg(cx, lint, expr.span, help, "consider using", suggestion, app); - } -} - -fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) - && value == Int(2) - && let Some(parent) = get_parent_expr(cx, expr) - { - if let Some(grandparent) = get_parent_expr(cx, parent) - && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind - && method.name == sym::sqrt - // we don't care about the applicability as this is an early-return condition - && detect_hypot(cx, receiver, &mut Applicability::Unspecified).is_some() - { - return; - } - - if let ExprKind::Binary( - Spanned { - node: op @ (BinOpKind::Add | BinOpKind::Sub), - .. - }, - lhs, - rhs, - ) = parent.kind - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - parent.span, - "multiply and add expressions can be calculated more efficiently and accurately", - |diag| { - let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; - - // Negate expr if original code has subtraction and expr is on the right side - let maybe_neg_sugg = |expr, hir_id, app: &mut _| { - let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); - if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { - -sugg - } else { - sugg - } - }; - - let mut app = Applicability::MachineApplicable; - diag.span_suggestion( - parent.span, - "consider using", - format!( - "{}.mul_add({}, {})", - Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(), - maybe_neg_sugg(receiver, expr.hir_id, &mut app), - maybe_neg_sugg(other_addend, other_addend.hir_id, &mut app), - ), - app, - ); - }, - ); - } - } -} - -fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applicability) -> Option { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Add, .. - }, - add_lhs, - add_rhs, - ) = receiver.kind - { - // check if expression of the form x * x + y * y - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - lmul_lhs, - lmul_rhs, - ) = add_lhs.kind - && let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - rmul_lhs, - rmul_rhs, - ) = add_rhs.kind - && eq_expr_value(cx, lmul_lhs, lmul_rhs) - && eq_expr_value(cx, rmul_lhs, rmul_rhs) - { - return Some(format!( - "{}.hypot({})", - Sugg::hir_with_applicability(cx, lmul_lhs, "_", app).maybe_paren(), - Sugg::hir_with_applicability(cx, rmul_lhs, "_", app) - )); - } - - // check if expression of the form x.powi(2) + y.powi(2) - if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = add_lhs.kind - && let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = add_rhs.kind - && lmethod.name == sym::powi - && rmethod.name == sym::powi - && let ecx = ConstEvalCtxt::new(cx) - && let Some(lvalue) = ecx.eval(largs_1) - && let Some(rvalue) = ecx.eval(rargs_1) - && Int(2) == lvalue - && Int(2) == rvalue - { - return Some(format!( - "{}.hypot({})", - Sugg::hir_with_applicability(cx, largs_0, "_", app).maybe_paren(), - Sugg::hir_with_applicability(cx, rargs_0, "_", app) - )); - } - } - - None -} - -fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { - let mut app = Applicability::MachineApplicable; - if let Some(message) = detect_hypot(cx, receiver, &mut app) { - span_lint_and_sugg( - cx, - IMPRECISE_FLOPS, - expr.span, - "hypotenuse can be computed more accurately", - "consider using", - message, - app, - ); - } -} - -// TODO: Lint expressions of the form `x.exp() - y` where y > 1 -// and suggest usage of `x.exp_m1() - (y - 1)` instead -fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Sub, .. - }, - lhs, - rhs, - ) = expr.kind - && let ExprKind::MethodCall(path, self_arg, [], _) = lhs.kind - && path.ident.name == sym::exp - && cx.typeck_results().expr_ty(lhs).is_floating_point() - && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs) - && (F32(1.0) == value || F64(1.0) == value) - && cx.typeck_results().expr_ty(self_arg).is_floating_point() - { - span_lint_and_then( - cx, - IMPRECISE_FLOPS, - expr.span, - "(e.pow(x) - 1) can be computed more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, self_arg, "_", &mut app).maybe_paren(); - diag.span_suggestion(expr.span, "consider using", format!("{recv}.exp_m1()"), app); - }, - ); - } -} - -fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - lhs, - rhs, - ) = expr.kind - && cx.typeck_results().expr_ty(lhs).is_floating_point() - && cx.typeck_results().expr_ty(rhs).is_floating_point() - { - return Some((lhs, rhs)); - } - - None -} - -fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: op @ (BinOpKind::Add | BinOpKind::Sub), - .. - }, - lhs, - rhs, - ) = &expr.kind - { - if let Some(parent) = get_parent_expr(cx, expr) - && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind - && method.name == sym::sqrt - // we don't care about the applicability as this is an early-return condition - && detect_hypot(cx, receiver, &mut Applicability::Unspecified).is_some() - { - return; - } - - let maybe_neg_sugg = |expr, app: &mut _| { - let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); - if let BinOpKind::Sub = op { -sugg } else { sugg } - }; - - let mut app = Applicability::MachineApplicable; - let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) - && cx.typeck_results().expr_ty(rhs).is_floating_point() - { - ( - inner_lhs, - Sugg::hir_with_applicability(cx, inner_rhs, "_", &mut app), - maybe_neg_sugg(rhs, &mut app), - ) - } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) - && cx.typeck_results().expr_ty(lhs).is_floating_point() - { - ( - inner_lhs, - maybe_neg_sugg(inner_rhs, &mut app), - Sugg::hir_with_applicability(cx, lhs, "_", &mut app), - ) - } else { - return; - }; - - // Check if any variable in the expression has an ambiguous type (could be f32 or f64) - // see: https://github.com/rust-lang/rust-clippy/issues/14897 - if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _))) - && has_ambiguous_literal_in_expr(cx, recv) - { - return; - } - - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "multiply and add expressions can be calculated more efficiently and accurately", - "consider using", - format!("{}.mul_add({arg1}, {arg2})", prepare_receiver_sugg(cx, recv, &mut app)), - app, - ); - } -} - -/// Returns true iff expr is an expression which tests whether or not -/// test is positive or an expression which tests whether or not test -/// is nonnegative. -/// Used for check-custom-abs function below -fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { - if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { - match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), - _ => false, - } - } else { - false - } -} - -/// See [`is_testing_positive`] -fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool { - if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind { - match op { - BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test), - BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test), - _ => false, - } - } else { - false - } -} - -/// Returns true iff expr is some zero literal -fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool { - match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) { - Some(Int(i)) => i == 0, - Some(F32(f)) => f == 0.0, - Some(F64(f)) => f == 0.0, - _ => false, - } -} - -/// If the two expressions are negations of each other, then it returns -/// a tuple, in which the first element is true iff expr1 is the -/// positive expressions, and the second element is the positive -/// one of the two expressions -/// If the two expressions are not negations of each other, then it -/// returns None. -fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { - if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind - && eq_expr_value(cx, expr1_negated, expr2) - { - return Some((false, expr2)); - } - if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind - && eq_expr_value(cx, expr1, expr2_negated) - { - return Some((true, expr1)); - } - None -} - -fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let Some(higher::If { - cond, - then, - r#else: Some(r#else), - }) = higher::If::hir(expr) - && let if_body_expr = peel_blocks(then) - && let else_body_expr = peel_blocks(r#else) - && let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr) - { - let sugg_positive_abs = if is_testing_positive(cx, cond, body) { - if_expr_positive - } else if is_testing_negative(cx, cond, body) { - !if_expr_positive - } else { - return; - }; - let mut app = Applicability::MachineApplicable; - let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren(); - let sugg = if sugg_positive_abs { - ("manual implementation of `abs` method", format!("{body}.abs()")) - } else { - #[rustfmt::skip] - ("manual implementation of negation of `abs` method", format!("-{body}.abs()")) - }; - span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app); - } -} - -fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { - if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind - && let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind - { - return method_a.name == method_b.name - && args_a.len() == args_b.len() - && (matches!(method_a.name, sym::ln | sym::log2 | sym::log10) - || method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])); - } - - false -} - -fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { - // check if expression of the form x.logN() / y.logN() - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Div, .. - }, - lhs, - rhs, - ) = expr.kind - && are_same_base_logs(cx, lhs, rhs) - && let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind - && let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind - { - let mut app = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "log base can be expressed more clearly", - "consider using", - format!( - "{}.log({})", - Sugg::hir_with_applicability(cx, largs_self, "_", &mut app).maybe_paren(), - Sugg::hir_with_applicability(cx, rargs_self, "_", &mut app), - ), - app, - ); - } -} - -fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Div, .. - }, - div_lhs, - div_rhs, - ) = expr.kind - && let ExprKind::Binary( - Spanned { - node: BinOpKind::Mul, .. - }, - mul_lhs, - mul_rhs, - ) = div_lhs.kind - && let ecx = ConstEvalCtxt::new(cx) - && let Some(rvalue) = ecx.eval(div_rhs) - && let Some(lvalue) = ecx.eval(mul_rhs) - { - // TODO: also check for constant values near PI/180 or 180/PI - if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) - && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "conversion to degrees can be done more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); - let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind - && let ast::LitKind::Float(ref value, float_type) = literal.node - && float_type == ast::LitFloatType::Unsuffixed - { - if value.as_str().ends_with('.') { - format!("{recv}0_f64.to_degrees()") - } else { - format!("{recv}_f64.to_degrees()") - } - } else { - format!("{}.to_degrees()", recv.maybe_paren()) - }; - diag.span_suggestion(expr.span, "consider using", proposal, app); - }, - ); - } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) - && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) - { - span_lint_and_then( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "conversion to radians can be done more accurately", - |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); - let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind - && let ast::LitKind::Float(ref value, float_type) = literal.node - && float_type == ast::LitFloatType::Unsuffixed - { - if value.as_str().ends_with('.') { - format!("{recv}0_f64.to_radians()") - } else { - format!("{recv}_f64.to_radians()") - } - } else { - format!("{}.to_radians()", recv.maybe_paren()) - }; - diag.span_suggestion(expr.span, "consider using", proposal, app); - }, - ); - } - } -} - impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // All of these operations are currently not const and are in std. @@ -750,22 +120,22 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { if recv_ty.is_floating_point() && !is_no_std_crate(cx) && cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) { match path.ident.name { - sym::ln => check_ln1p(cx, expr, receiver), - sym::log => check_log_base(cx, expr, receiver, args), - sym::powf => check_powf(cx, expr, receiver, args), - sym::powi => check_powi(cx, expr, receiver, args), - sym::sqrt => check_hypot(cx, expr, receiver), + sym::ln => ln1p::check(cx, expr, receiver), + sym::log => log_base::check(cx, expr, receiver, args), + sym::powf => powf::check(cx, expr, receiver, args), + sym::powi => powi::check(cx, expr, receiver, args), + sym::sqrt => hypot::check(cx, expr, receiver), _ => {}, } } } else { if !is_no_std_crate(cx) { - check_expm1(cx, expr); - check_mul_add(cx, expr); - check_custom_abs(cx, expr); - check_log_division(cx, expr); + expm1::check(cx, expr); + mul_add::check(cx, expr); + custom_abs::check(cx, expr); + log_division::check(cx, expr); } - check_radians(cx, expr); + radians::check(cx, expr); } } } diff --git a/clippy_lints/src/floating_point_arithmetic/mul_add.rs b/clippy_lints/src/floating_point_arithmetic/mul_add.rs new file mode 100644 index 000000000000..03a9d3b05f88 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/mul_add.rs @@ -0,0 +1,94 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, has_ambiguous_literal_in_expr, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + lhs, + rhs, + ) = expr.kind + && cx.typeck_results().expr_ty(lhs).is_floating_point() + && cx.typeck_results().expr_ty(rhs).is_floating_point() + { + return Some((lhs, rhs)); + } + + None +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: op @ (BinOpKind::Add | BinOpKind::Sub), + .. + }, + lhs, + rhs, + ) = &expr.kind + { + if let Some(parent) = get_parent_expr(cx, expr) + && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind + && method.name == sym::sqrt + // we don't care about the applicability as this is an early-return condition + && super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some() + { + return; + } + + let maybe_neg_sugg = |expr, app: &mut _| { + let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); + if let BinOpKind::Sub = op { -sugg } else { sugg } + }; + + let mut app = Applicability::MachineApplicable; + let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) + && cx.typeck_results().expr_ty(rhs).is_floating_point() + { + ( + inner_lhs, + Sugg::hir_with_applicability(cx, inner_rhs, "_", &mut app), + maybe_neg_sugg(rhs, &mut app), + ) + } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) + && cx.typeck_results().expr_ty(lhs).is_floating_point() + { + ( + inner_lhs, + maybe_neg_sugg(inner_rhs, &mut app), + Sugg::hir_with_applicability(cx, lhs, "_", &mut app), + ) + } else { + return; + }; + + // Check if any variable in the expression has an ambiguous type (could be f32 or f64) + // see: https://github.com/rust-lang/rust-clippy/issues/14897 + if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _))) + && has_ambiguous_literal_in_expr(cx, recv) + { + return; + } + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "multiply and add expressions can be calculated more efficiently and accurately", + "consider using", + format!( + "{}.mul_add({arg1}, {arg2})", + super::lib::prepare_receiver_sugg(cx, recv, &mut app) + ), + app, + ); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/powf.rs b/clippy_lints/src/floating_point_arithmetic/powf.rs new file mode 100644 index 000000000000..8e4a7388e784 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/powf.rs @@ -0,0 +1,94 @@ +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::numeric_literal; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; + +use super::{IMPRECISE_FLOPS, SUBOPTIMAL_FLOPS}; + +// Returns an integer if the float constant is a whole number and it can be +// converted to an integer without loss of precision. For now we only check +// ranges [-16777215, 16777216) for type f32 as whole number floats outside +// this range are lossy and ambiguous. +#[expect(clippy::cast_possible_truncation)] +fn get_integer_from_float_constant(value: &Constant) -> Option { + match value { + F32(num) if num.fract() == 0.0 => { + if (-16_777_215.0..16_777_216.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + F64(num) if num.fract() == 0.0 => { + if (-2_147_483_648.0..2_147_483_648.0).contains(num) { + Some(num.round() as i32) + } else { + None + } + }, + _ => None, + } +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { + // Check receiver + if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) + && let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + Some("exp") + } else if F32(2.0) == value || F64(2.0) == value { + Some("exp2") + } else { + None + } + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "exponent for bases 2 and e can be computed more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = super::lib::prepare_receiver_sugg(cx, &args[0], &mut app); + diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app); + }, + ); + } + + // Check argument + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(); + let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { + ( + SUBOPTIMAL_FLOPS, + "square-root of a number can be computed more efficiently and accurately", + format!("{recv}.sqrt()"), + ) + } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { + ( + IMPRECISE_FLOPS, + "cube-root of a number can be computed more accurately", + format!("{recv}.cbrt()"), + ) + } else if let Some(exponent) = get_integer_from_float_constant(&value) { + ( + SUBOPTIMAL_FLOPS, + "exponentiation with integer powers can be computed more efficiently", + format!( + "{recv}.powi({})", + numeric_literal::format(&exponent.to_string(), None, false) + ), + ) + } else { + return; + }; + + span_lint_and_sugg(cx, lint, expr.span, help, "consider using", suggestion, app); + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/powi.rs b/clippy_lints/src/floating_point_arithmetic/powi.rs new file mode 100644 index 000000000000..a61a2a82c023 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/powi.rs @@ -0,0 +1,70 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::Int; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::SUBOPTIMAL_FLOPS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) + && value == Int(2) + && let Some(parent) = get_parent_expr(cx, expr) + { + if let Some(grandparent) = get_parent_expr(cx, parent) + && let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind + && method.name == sym::sqrt + // we don't care about the applicability as this is an early-return condition + && super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some() + { + return; + } + + if let ExprKind::Binary( + Spanned { + node: op @ (BinOpKind::Add | BinOpKind::Sub), + .. + }, + lhs, + rhs, + ) = parent.kind + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + parent.span, + "multiply and add expressions can be calculated more efficiently and accurately", + |diag| { + let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; + + // Negate expr if original code has subtraction and expr is on the right side + let maybe_neg_sugg = |expr, hir_id, app: &mut _| { + let sugg = Sugg::hir_with_applicability(cx, expr, "_", app); + if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { + -sugg + } else { + sugg + } + }; + + let mut app = Applicability::MachineApplicable; + diag.span_suggestion( + parent.span, + "consider using", + format!( + "{}.mul_add({}, {})", + Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(), + maybe_neg_sugg(receiver, expr.hir_id, &mut app), + maybe_neg_sugg(other_addend, other_addend.hir_id, &mut app), + ), + app, + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/floating_point_arithmetic/radians.rs b/clippy_lints/src/floating_point_arithmetic/radians.rs new file mode 100644 index 000000000000..2021f00a97e8 --- /dev/null +++ b/clippy_lints/src/floating_point_arithmetic/radians.rs @@ -0,0 +1,89 @@ +use clippy_utils::consts::ConstEvalCtxt; +use clippy_utils::consts::Constant::{F32, F64}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; + +use super::SUBOPTIMAL_FLOPS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Div, .. + }, + div_lhs, + div_rhs, + ) = expr.kind + && let ExprKind::Binary( + Spanned { + node: BinOpKind::Mul, .. + }, + mul_lhs, + mul_rhs, + ) = div_lhs.kind + && let ecx = ConstEvalCtxt::new(cx) + && let Some(rvalue) = ecx.eval(div_rhs) + && let Some(lvalue) = ecx.eval(mul_rhs) + { + // TODO: also check for constant values near PI/180 or 180/PI + if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) + && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "conversion to degrees can be done more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); + let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind + && let ast::LitKind::Float(ref value, float_type) = literal.node + && float_type == ast::LitFloatType::Unsuffixed + { + if value.as_str().ends_with('.') { + format!("{recv}0_f64.to_degrees()") + } else { + format!("{recv}_f64.to_degrees()") + } + } else { + format!("{}.to_degrees()", recv.maybe_paren()) + }; + diag.span_suggestion(expr.span, "consider using", proposal, app); + }, + ); + } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) + && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) + { + span_lint_and_then( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "conversion to radians can be done more accurately", + |diag| { + let mut app = Applicability::MachineApplicable; + let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app); + let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind + && let ast::LitKind::Float(ref value, float_type) = literal.node + && float_type == ast::LitFloatType::Unsuffixed + { + if value.as_str().ends_with('.') { + format!("{recv}0_f64.to_radians()") + } else { + format!("{recv}_f64.to_radians()") + } + } else { + format!("{}.to_radians()", recv.maybe_paren()) + }; + diag.span_suggestion(expr.span, "consider using", proposal, app); + }, + ); + } + } +} From 7d0b1e144917b46d3e2b5c18ef6d434b098dab08 Mon Sep 17 00:00:00 2001 From: Coca Date: Fri, 2 Jan 2026 20:59:22 +0000 Subject: [PATCH 040/273] `transmuting_null`: Add checks for `without_provenance` and `without_provenance_mut` Currently `without_provenance`/`without_provenance_mut` do not have a `rustc_diagnostic_item` so this change is dependent on them being added before being ready to be used. changelog: [`transmuting_null`]: now checks for [`ptr::without_provenance`](https://doc.rust-lang.org/core/ptr/fn.without_provenance.html) and [`ptr::without_provenance_mut`](https://doc.rust-lang.org/core/ptr/fn.without_provenance_mut.html) which create null pointers --- clippy_lints/src/transmute/transmuting_null.rs | 12 ++++++++++++ tests/ui/transmuting_null.rs | 11 +++++++++++ tests/ui/transmuting_null.stderr | 14 +++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/transmute/transmuting_null.rs b/clippy_lints/src/transmute/transmuting_null.rs index 3f435f255d91..4f06d98703f6 100644 --- a/clippy_lints/src/transmute/transmuting_null.rs +++ b/clippy_lints/src/transmute/transmuting_null.rs @@ -42,6 +42,18 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t return true; } + // Catching: + // `std::mem::transmute(std::ptr::without_provenance::(0))` + // `std::mem::transmute(std::ptr::without_provenance_mut::(0))` + if let ExprKind::Call(func1, [arg1]) = arg.kind + && (func1.basic_res().is_diag_item(cx, sym::ptr_without_provenance) + || func1.basic_res().is_diag_item(cx, sym::ptr_without_provenance_mut)) + && is_integer_const(cx, arg1, 0) + { + span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG); + return true; + } + // Catching: // `std::mem::transmute({ 0 as *const u64 })` and similar const blocks if let ExprKind::Block(block, _) = arg.kind diff --git a/tests/ui/transmuting_null.rs b/tests/ui/transmuting_null.rs index 00aa35dff803..efa4c5cfdc2d 100644 --- a/tests/ui/transmuting_null.rs +++ b/tests/ui/transmuting_null.rs @@ -47,9 +47,20 @@ fn transumute_single_expr_blocks() { } } +fn transmute_pointer_creators() { + unsafe { + let _: &u64 = std::mem::transmute(std::ptr::without_provenance::(0)); + //~^ transmuting_null + + let _: &u64 = std::mem::transmute(std::ptr::without_provenance_mut::(0)); + //~^ transmuting_null + } +} + fn main() { one_liners(); transmute_const(); transmute_const_int(); transumute_single_expr_blocks(); + transmute_pointer_creators(); } diff --git a/tests/ui/transmuting_null.stderr b/tests/ui/transmuting_null.stderr index e1de391813bd..3c6c28f31d0d 100644 --- a/tests/ui/transmuting_null.stderr +++ b/tests/ui/transmuting_null.stderr @@ -37,5 +37,17 @@ error: transmuting a known null pointer into a reference LL | let _: &u64 = std::mem::transmute(const { u64::MIN as *const u64 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 6 previous errors +error: transmuting a known null pointer into a reference + --> tests/ui/transmuting_null.rs:52:23 + | +LL | let _: &u64 = std::mem::transmute(std::ptr::without_provenance::(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmuting a known null pointer into a reference + --> tests/ui/transmuting_null.rs:55:23 + | +LL | let _: &u64 = std::mem::transmute(std::ptr::without_provenance_mut::(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors From 6a9dae4f3e679c298c6d6ad83f65d2d9c51a02f0 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 9 Jan 2026 10:37:00 +0100 Subject: [PATCH 041/273] Merge commit '500e0ff18726cd44b23004a02fc1b99f52c11ab1' into clippy-subtree-update --- COPYRIGHT | 2 +- Cargo.toml | 2 +- LICENSE-APACHE | 2 +- LICENSE-MIT | 2 +- README.md | 2 +- clippy_lints/src/attrs/allow_attributes.rs | 9 +- clippy_lints/src/attrs/mod.rs | 6 +- .../src/attrs/should_panic_without_expect.rs | 2 +- clippy_lints/src/bool_assert_comparison.rs | 33 +++-- clippy_lints/src/booleans.rs | 3 +- clippy_lints/src/cargo/mod.rs | 2 +- clippy_lints/src/cfg_not_test.rs | 13 +- clippy_lints/src/checked_conversions.rs | 5 +- .../src/derive/derive_ord_xor_partial_ord.rs | 9 +- clippy_lints/src/derive/mod.rs | 2 +- .../src/doc/include_in_doc_without_cfg.rs | 2 +- clippy_lints/src/doc/mod.rs | 10 +- clippy_lints/src/double_parens.rs | 2 + clippy_lints/src/implicit_saturating_sub.rs | 27 +++- clippy_lints/src/incompatible_msrv.rs | 10 +- clippy_lints/src/inherent_impl.rs | 23 ++- clippy_lints/src/large_include_file.rs | 2 +- clippy_lints/src/large_stack_arrays.rs | 9 ++ clippy_lints/src/loops/for_kv_map.rs | 24 +++- clippy_lints/src/loops/mod.rs | 2 +- clippy_lints/src/loops/never_loop.rs | 4 +- clippy_lints/src/manual_ignore_case_cmp.rs | 10 +- clippy_lints/src/manual_ilog2.rs | 4 +- clippy_lints/src/matches/manual_ok_err.rs | 2 +- clippy_lints/src/matches/match_as_ref.rs | 5 +- clippy_lints/src/matches/match_bool.rs | 96 ++++++------- .../src/matches/redundant_pattern_match.rs | 23 ++- clippy_lints/src/methods/is_empty.rs | 5 +- clippy_lints/src/methods/iter_kv_map.rs | 7 +- clippy_lints/src/methods/unnecessary_fold.rs | 111 +++++++++----- .../src/methods/unnecessary_to_owned.rs | 22 +-- .../src/missing_enforced_import_rename.rs | 3 +- clippy_lints/src/multiple_bound_locations.rs | 2 +- clippy_lints/src/mutex_atomic.rs | 2 +- clippy_lints/src/needless_bool.rs | 6 +- clippy_lints/src/needless_for_each.rs | 125 +++++++++------- clippy_lints/src/new_without_default.rs | 36 +++-- clippy_lints/src/operators/cmp_owned.rs | 58 +++----- clippy_lints/src/operators/manual_div_ceil.rs | 127 +++++++++------- .../src/operators/manual_is_multiple_of.rs | 4 +- clippy_lints/src/question_mark.rs | 7 +- clippy_lints/src/single_range_in_vec_init.rs | 2 +- clippy_lints/src/strings.rs | 8 +- clippy_lints/src/strlen_on_c_strings.rs | 4 +- .../src/transmute/transmuting_null.rs | 20 ++- clippy_lints/src/useless_conversion.rs | 14 +- clippy_lints/src/utils/author.rs | 1 - .../src/lint_without_lint_pass.rs | 4 +- clippy_utils/README.md | 4 +- clippy_utils/src/ast_utils/mod.rs | 4 +- clippy_utils/src/attrs.rs | 6 +- clippy_utils/src/consts.rs | 8 +- clippy_utils/src/hir_utils.rs | 4 +- clippy_utils/src/lib.rs | 29 +++- clippy_utils/src/res.rs | 4 +- clippy_utils/src/sugg.rs | 2 +- clippy_utils/src/sym.rs | 1 + rust-toolchain.toml | 2 +- rustc_tools_util/README.md | 2 +- .../clippy.toml | 3 +- .../conf_missing_enforced_import_rename.fixed | 1 + .../conf_missing_enforced_import_rename.rs | 1 + ...conf_missing_enforced_import_rename.stderr | 2 +- tests/ui/bool_assert_comparison.fixed | 13 ++ tests/ui/bool_assert_comparison.rs | 13 ++ tests/ui/bool_assert_comparison.stderr | 26 +++- tests/ui/checked_conversions.fixed | 16 +++ tests/ui/checked_conversions.rs | 16 +++ tests/ui/checked_conversions.stderr | 42 +++--- tests/ui/cmp_owned/with_suggestion.fixed | 29 ++++ tests/ui/cmp_owned/with_suggestion.rs | 29 ++++ tests/ui/cmp_owned/with_suggestion.stderr | 8 +- tests/ui/derive_ord_xor_partial_ord.rs | 14 ++ tests/ui/double_parens.fixed | 16 +++ tests/ui/double_parens.rs | 16 +++ tests/ui/for_kv_map.fixed | 17 +++ tests/ui/for_kv_map.rs | 17 +++ tests/ui/for_kv_map.stderr | 14 +- tests/ui/impl.rs | 42 +++++- tests/ui/impl.stderr | 90 ++++++++++-- tests/ui/implicit_saturating_sub.fixed | 8 ++ tests/ui/implicit_saturating_sub.rs | 8 ++ tests/ui/implicit_saturating_sub.stderr | 8 +- tests/ui/iter_kv_map.fixed | 6 + tests/ui/iter_kv_map.rs | 6 + tests/ui/iter_kv_map.stderr | 8 +- tests/ui/manual_div_ceil.fixed | 29 ++++ tests/ui/manual_div_ceil.rs | 29 ++++ tests/ui/manual_div_ceil.stderr | 20 ++- tests/ui/manual_div_ceil_with_feature.fixed | 29 ++++ tests/ui/manual_div_ceil_with_feature.rs | 29 ++++ tests/ui/manual_div_ceil_with_feature.stderr | 20 ++- tests/ui/manual_ignore_case_cmp.fixed | 20 +++ tests/ui/manual_ignore_case_cmp.rs | 20 +++ tests/ui/manual_ignore_case_cmp.stderr | 26 +++- tests/ui/manual_ilog2.fixed | 17 +++ tests/ui/manual_ilog2.rs | 17 +++ tests/ui/manual_ilog2.stderr | 14 +- tests/ui/manual_is_multiple_of.fixed | 16 +++ tests/ui/manual_is_multiple_of.rs | 16 +++ tests/ui/manual_is_multiple_of.stderr | 8 +- tests/ui/manual_ok_err.fixed | 10 ++ tests/ui/manual_ok_err.rs | 14 ++ tests/ui/manual_ok_err.stderr | 13 +- tests/ui/match_as_ref.fixed | 10 ++ tests/ui/match_as_ref.rs | 14 ++ tests/ui/match_as_ref.stderr | 23 ++- tests/ui/match_bool.fixed | 11 ++ tests/ui/match_bool.rs | 15 ++ tests/ui/match_bool.stderr | 12 +- tests/ui/mutex_atomic.fixed | 12 ++ tests/ui/mutex_atomic.rs | 12 ++ tests/ui/mutex_atomic.stderr | 10 +- tests/ui/needless_bool_assign.fixed | 19 +++ tests/ui/needless_bool_assign.rs | 23 +++ tests/ui/needless_bool_assign.stderr | 12 +- tests/ui/needless_for_each_fixable.fixed | 8 ++ tests/ui/needless_for_each_fixable.rs | 8 ++ tests/ui/needless_for_each_fixable.stderr | 19 ++- tests/ui/never_loop_iterator_reduction.rs | 9 +- tests/ui/never_loop_iterator_reduction.stderr | 2 +- tests/ui/new_without_default.fixed | 55 +++++++ tests/ui/new_without_default.rs | 36 +++++ tests/ui/new_without_default.stderr | 55 ++++++- tests/ui/question_mark.fixed | 17 ++- tests/ui/question_mark.rs | 22 ++- tests/ui/question_mark.stderr | 19 ++- .../redundant_pattern_matching_option.fixed | 13 ++ tests/ui/redundant_pattern_matching_option.rs | 13 ++ .../redundant_pattern_matching_option.stderr | 14 +- .../redundant_pattern_matching_result.fixed | 20 +++ tests/ui/redundant_pattern_matching_result.rs | 24 ++++ .../redundant_pattern_matching_result.stderr | 19 ++- .../ui/single_range_in_vec_init_unfixable.rs | 12 ++ .../single_range_in_vec_init_unfixable.stderr | 16 +++ tests/ui/str_to_string.fixed | 14 ++ tests/ui/str_to_string.rs | 14 ++ tests/ui/str_to_string.stderr | 8 +- tests/ui/string_from_utf8_as_bytes.fixed | 10 ++ tests/ui/string_from_utf8_as_bytes.rs | 10 ++ tests/ui/string_from_utf8_as_bytes.stderr | 10 +- tests/ui/transmuting_null.rs | 11 ++ tests/ui/transmuting_null.stderr | 14 +- tests/ui/unnecessary_fold.fixed | 11 ++ tests/ui/unnecessary_fold.rs | 11 ++ tests/ui/unnecessary_fold.stderr | 17 ++- tests/ui/unnecessary_to_owned.fixed | 9 ++ tests/ui/unnecessary_to_owned.rs | 9 ++ tests/ui/unnecessary_to_owned.stderr | 8 +- tests/ui/useless_conversion.fixed | 10 ++ tests/ui/useless_conversion.rs | 10 ++ tests/ui/useless_conversion.stderr | 135 ++++++++++-------- triagebot.toml | 1 - 158 files changed, 2147 insertions(+), 518 deletions(-) create mode 100644 tests/ui/single_range_in_vec_init_unfixable.rs create mode 100644 tests/ui/single_range_in_vec_init_unfixable.stderr diff --git a/COPYRIGHT b/COPYRIGHT index f402dcf465a3..d3b4c9e5fb2c 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,6 +1,6 @@ // REUSE-IgnoreStart -Copyright 2014-2025 The Rust Project Developers +Copyright (c) The Rust Project Contributors Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/Cargo.toml b/Cargo.toml index 67078adea2b4..7379dcbb7b37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } -askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] } +askama = { version = "0.15", default-features = false, features = ["alloc", "config", "derive"] } [dev-dependencies.toml] version = "0.9.7" diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 9990a0cec474..773ae298cc19 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright 2014-2025 The Rust Project Developers +Copyright (c) The Rust Project Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index 5d6e36ef6bfc..9549420685cc 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-2025 The Rust Project Developers +Copyright (c) The Rust Project Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index 78498c73ae78..8bccd040c1f9 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT -Copyright 2014-2025 The Rust Project Developers +Copyright (c) The Rust Project Contributors Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/clippy_lints/src/attrs/allow_attributes.rs b/clippy_lints/src/attrs/allow_attributes.rs index 84b65d3185e3..0235b130b6c0 100644 --- a/clippy_lints/src/attrs/allow_attributes.rs +++ b/clippy_lints/src/attrs/allow_attributes.rs @@ -1,10 +1,10 @@ use super::ALLOW_ATTRIBUTES; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_from_proc_macro; +use rustc_ast::attr::AttributeExt; use rustc_ast::{AttrStyle, Attribute}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, LintContext}; -use rustc_ast::attr::AttributeExt; // Separate each crate's features. pub fn check<'cx>(cx: &EarlyContext<'cx>, attr: &'cx Attribute) { @@ -15,12 +15,7 @@ pub fn check<'cx>(cx: &EarlyContext<'cx>, attr: &'cx Attribute) { { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then(cx, ALLOW_ATTRIBUTES, path_span, "#[allow] attribute found", |diag| { - diag.span_suggestion( - path_span, - "replace it with", - "expect", - Applicability::MachineApplicable, - ); + diag.span_suggestion(path_span, "replace it with", "expect", Applicability::MachineApplicable); }); } } diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 366f5873a1aa..42c321df61c1 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -16,7 +16,7 @@ mod utils; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv, MsrvStack}; -use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind, AttrItemKind}; +use rustc_ast::{self as ast, AttrArgs, AttrItemKind, AttrKind, Attribute, MetaItemInner, MetaItemKind}; use rustc_hir::{ImplItem, Item, ItemKind, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -604,7 +604,9 @@ impl EarlyLintPass for PostExpansionEarlyAttributes { if attr.has_name(sym::ignore) && match &attr.kind { - AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrItemKind::Unparsed(AttrArgs::Eq { .. })), + AttrKind::Normal(normal_attr) => { + !matches!(normal_attr.item.args, AttrItemKind::Unparsed(AttrArgs::Eq { .. })) + }, AttrKind::DocComment(..) => true, } { diff --git a/clippy_lints/src/attrs/should_panic_without_expect.rs b/clippy_lints/src/attrs/should_panic_without_expect.rs index b854a3070bef..1a9abd88a46a 100644 --- a/clippy_lints/src/attrs/should_panic_without_expect.rs +++ b/clippy_lints/src/attrs/should_panic_without_expect.rs @@ -2,7 +2,7 @@ use super::{Attribute, SHOULD_PANIC_WITHOUT_EXPECT}; use clippy_utils::diagnostics::span_lint_and_sugg; use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::TokenTree; -use rustc_ast::{AttrArgs, AttrKind, AttrItemKind}; +use rustc_ast::{AttrArgs, AttrItemKind, AttrKind}; use rustc_errors::Applicability; use rustc_lint::EarlyContext; use rustc_span::sym; diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index f31b67f470f9..165941a859f7 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node}; +use clippy_utils::source::walk_span_to_context; use clippy_utils::sugg::Sugg; use clippy_utils::sym; use clippy_utils::ty::{implements_trait, is_copy}; @@ -130,22 +131,24 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison { let mut suggestions = vec![(name_span, non_eq_mac.to_string()), (lit_span, String::new())]; - if let Some(sugg) = Sugg::hir_opt(cx, non_lit_expr) { - let sugg = if bool_value ^ eq_macro { - !sugg.maybe_paren() - } else if ty::Bool == *non_lit_ty.kind() { - sugg - } else { - !!sugg.maybe_paren() - }; - suggestions.push((non_lit_expr.span, sugg.to_string())); + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, non_lit_expr, macro_call.span.ctxt(), "..", &mut applicability); + let sugg = if bool_value ^ eq_macro { + !sugg.maybe_paren() + } else if ty::Bool == *non_lit_ty.kind() { + sugg + } else { + !!sugg.maybe_paren() + }; + let non_lit_expr_span = + walk_span_to_context(non_lit_expr.span, macro_call.span.ctxt()).unwrap_or(non_lit_expr.span); + suggestions.push((non_lit_expr_span, sugg.to_string())); - diag.multipart_suggestion( - format!("replace it with `{non_eq_mac}!(..)`"), - suggestions, - Applicability::MachineApplicable, - ); - } + diag.multipart_suggestion( + format!("replace it with `{non_eq_mac}!(..)`"), + suggestions, + applicability, + ); }, ); } diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index a04a56d72bc0..0bd459d8b021 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -15,6 +15,7 @@ use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, Symbol, SyntaxContext}; +use std::fmt::Write as _; declare_clippy_lint! { /// ### What it does @@ -356,7 +357,7 @@ impl SuggestContext<'_, '_, '_> { if app != Applicability::MachineApplicable { return None; } - self.output.push_str(&(!snip).to_string()); + let _cannot_fail = write!(&mut self.output, "{}", &(!snip)); } }, True | False | Not(_) => { diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs index 60371dcd7715..08d92adbacef 100644 --- a/clippy_lints/src/cargo/mod.rs +++ b/clippy_lints/src/cargo/mod.rs @@ -132,7 +132,7 @@ declare_clippy_lint! { /// Because this can be caused purely by the dependencies /// themselves, it's not always possible to fix this issue. /// In those cases, you can allow that specific crate using - /// the `allowed_duplicate_crates` configuration option. + /// the `allowed-duplicate-crates` configuration option. /// /// ### Example /// ```toml diff --git a/clippy_lints/src/cfg_not_test.rs b/clippy_lints/src/cfg_not_test.rs index ec543d02c9dd..88d07be0d4d4 100644 --- a/clippy_lints/src/cfg_not_test.rs +++ b/clippy_lints/src/cfg_not_test.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; +use rustc_ast::attr::data_structures::CfgEntry; +use rustc_ast::{AttrItemKind, EarlyParsedAttribute}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; -use rustc_ast::AttrItemKind; -use rustc_ast::EarlyParsedAttribute; use rustc_span::sym; -use rustc_ast::attr::data_structures::CfgEntry; declare_clippy_lint! { /// ### What it does @@ -40,7 +39,7 @@ impl EarlyLintPass for CfgNotTest { unreachable!() }; - if contains_not_test(&cfg, false) { + if contains_not_test(cfg, false) { span_lint_and_then( cx, CFG_NOT_TEST, @@ -58,11 +57,9 @@ impl EarlyLintPass for CfgNotTest { fn contains_not_test(cfg: &CfgEntry, not: bool) -> bool { match cfg { - CfgEntry::All(subs, _) | CfgEntry::Any(subs, _) => subs.iter().any(|item| { - contains_not_test(item, not) - }), + CfgEntry::All(subs, _) | CfgEntry::Any(subs, _) => subs.iter().any(|item| contains_not_test(item, not)), CfgEntry::Not(sub, _) => contains_not_test(sub, !not), CfgEntry::NameValue { name: sym::test, .. } => not, - _ => false + _ => false, } } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 9b3822f9d8f0..8303897d1294 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal, sym}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind}; @@ -80,7 +80,8 @@ impl LateLintPass<'_> for CheckedConversions { && self.msrv.meets(cx, msrvs::TRY_FROM) { let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability); + let (snippet, _) = + snippet_with_context(cx, cv.expr_to_cast.span, item.span.ctxt(), "_", &mut applicability); span_lint_and_sugg( cx, CHECKED_CONVERSIONS, diff --git a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs index 2bd5e2cbfb1a..316d800a70c9 100644 --- a/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs +++ b/clippy_lints/src/derive/derive_ord_xor_partial_ord.rs @@ -1,15 +1,16 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::fulfill_or_allowed; use rustc_hir::{self as hir, HirId}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; -use rustc_span::{Span, sym}; +use rustc_span::sym; use super::DERIVE_ORD_XOR_PARTIAL_ORD; /// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - span: Span, + item: &hir::Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>, adt_hir_id: HirId, @@ -19,6 +20,8 @@ pub(super) fn check<'tcx>( && let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait() && let Some(def_id) = &trait_ref.trait_def_id() && *def_id == ord_trait_def_id + && let item_hir_id = cx.tcx.local_def_id_to_hir_id(item.owner_id) + && !fulfill_or_allowed(cx, DERIVE_ORD_XOR_PARTIAL_ORD, [adt_hir_id]) { // Look for the PartialOrd implementations for `ty` cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { @@ -39,7 +42,7 @@ pub(super) fn check<'tcx>( "you are deriving `Ord` but have implemented `PartialOrd` explicitly" }; - span_lint_hir_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, adt_hir_id, span, mess, |diag| { + span_lint_hir_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, item_hir_id, item.span, mess, |diag| { if let Some(local_def_id) = impl_id.as_local() { let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here"); diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index eafe7c4bb9f2..86614201c406 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -208,7 +208,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive { let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); - derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived); + derive_ord_xor_partial_ord::check(cx, item, trait_ref, ty, adt_hir_id, is_automatically_derived); if is_automatically_derived { unsafe_derive_deserialize::check(cx, item, trait_ref, ty, adt_hir_id); diff --git a/clippy_lints/src/doc/include_in_doc_without_cfg.rs b/clippy_lints/src/doc/include_in_doc_without_cfg.rs index f8e9e870f629..6c800f47b68a 100644 --- a/clippy_lints/src/doc/include_in_doc_without_cfg.rs +++ b/clippy_lints/src/doc/include_in_doc_without_cfg.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use rustc_ast::{AttrArgs, AttrKind, AttrStyle, Attribute, AttrItemKind}; +use rustc_ast::{AttrArgs, AttrItemKind, AttrKind, AttrStyle, Attribute}; use rustc_errors::Applicability; use rustc_lint::EarlyContext; diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index b11b2f8392c1..2b41275ee3a4 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -869,10 +869,12 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ }), true, ); - let mut doc = fragments.iter().fold(String::new(), |mut acc, fragment| { - add_doc_fragment(&mut acc, fragment); - acc - }); + + let mut doc = String::with_capacity(fragments.iter().map(|frag| frag.doc.as_str().len() + 1).sum()); + + for fragment in &fragments { + add_doc_fragment(&mut doc, fragment); + } doc.pop(); if doc.trim().is_empty() { diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index 8defbeeaa5f2..351d29d87432 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -114,6 +114,8 @@ fn check_source(cx: &EarlyContext<'_>, inner: &Expr) -> bool { && inner.starts_with('(') && inner.ends_with(')') && outer_after_inner.trim_start().starts_with(')') + // Don't lint macro repetition patterns like `($($result),*)` where parens are necessary + && !inner.trim_start_matches('(').trim_start().starts_with("$(") { true } else { diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 7b6f8729cb75..516f9e3aa60c 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -1,9 +1,13 @@ +use std::borrow::Cow; + use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::{Sugg, make_binop}; use clippy_utils::{ - SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt, sym, + SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, is_integer_literal_untyped, + peel_blocks, peel_blocks_with_stmt, sym, }; use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; @@ -238,10 +242,21 @@ fn check_subtraction( if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) { // This part of the condition is voluntarily split from the one before to ensure that // if `snippet_opt` fails, it won't try the next conditions. - if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST)) - && let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren) - && let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr) - { + if !is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST) { + let mut applicability = Applicability::MachineApplicable; + let big_expr_sugg = (if is_integer_literal_untyped(big_expr) { + let get_snippet = |span: Span| { + let snippet = snippet_with_applicability(cx, span, "..", &mut applicability); + let big_expr_ty = cx.typeck_results().expr_ty(big_expr); + Cow::Owned(format!("{snippet}_{big_expr_ty}")) + }; + Sugg::hir_from_snippet(cx, big_expr, get_snippet) + } else { + Sugg::hir_with_applicability(cx, big_expr, "..", &mut applicability) + }) + .maybe_paren(); + let little_expr_sugg = Sugg::hir_with_applicability(cx, little_expr, "..", &mut applicability); + let sugg = format!( "{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}", if is_composited { "{ " } else { "" }, @@ -254,7 +269,7 @@ fn check_subtraction( "manual arithmetic check found", "replace it with", sugg, - Applicability::MachineApplicable, + applicability, ); } } else if eq_expr_value(cx, left, little_expr) diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index e0149a23fccf..8d538ff1acba 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -3,14 +3,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::{is_in_const_context, is_in_test, sym}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince}; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince, find_attr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::{ExpnKind, Span}; -use rustc_hir::attrs::AttributeKind; -use rustc_hir::find_attr; declare_clippy_lint! { /// ### What it does @@ -270,6 +269,9 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { /// attribute. fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool { cx.tcx.hir_parent_id_iter(hir_id).any(|id| { - find_attr!(cx.tcx.hir_attrs(id), AttributeKind::CfgTrace(..) | AttributeKind::CfgAttrTrace) + find_attr!( + cx.tcx.hir_attrs(id), + AttributeKind::CfgTrace(..) | AttributeKind::CfgAttrTrace + ) }) } diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index f59c7615d745..14928a1be13b 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -101,7 +101,21 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { InherentImplLintScope::Crate => Criterion::Crate, }; let is_test = is_cfg_test(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id); - match type_map.entry((impl_ty, criterion, is_test)) { + let predicates = { + // Gets the predicates (bounds) for the given impl block, + // sorted for consistent comparison to allow distinguishing between impl blocks + // with different generic bounds. + let mut predicates = cx + .tcx + .predicates_of(impl_id) + .predicates + .iter() + .map(|(clause, _)| *clause) + .collect::>(); + predicates.sort_by_key(|c| format!("{c:?}")); + predicates + }; + match type_map.entry((impl_ty, predicates, criterion, is_test)) { Entry::Vacant(e) => { // Store the id for the first impl block of this type. The span is retrieved lazily. e.insert(IdOrSpan::Id(impl_id)); @@ -152,15 +166,12 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option { let id = cx.tcx.local_def_id_to_hir_id(id); if let Node::Item(&Item { - kind: ItemKind::Impl(impl_item), + kind: ItemKind::Impl(_), span, .. }) = cx.tcx.hir_node(id) { - (!span.from_expansion() - && impl_item.generics.params.is_empty() - && !fulfill_or_allowed(cx, MULTIPLE_INHERENT_IMPL, [id])) - .then_some(span) + (!span.from_expansion() && !fulfill_or_allowed(cx, MULTIPLE_INHERENT_IMPL, [id])).then_some(span) } else { None } diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 5c37747b8c9b..d77e0beeaf4c 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -3,7 +3,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::snippet_opt; -use rustc_ast::{AttrArgs, AttrKind, Attribute, LitKind}; +use rustc_ast::{AttrArgs, AttrItemKind, AttrKind, Attribute, LitKind}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index 261b03abba17..32c23a2d0362 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -94,6 +94,15 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { }) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size) { + // libtest might generate a large array containing the test cases, and no span will be associated + // to it. In this case it is better not to complain. + // + // Note that this condition is not checked explicitly by a unit test. Do not remove it without + // ensuring that stays fixed. + if expr.span.is_dummy() { + return; + } + span_lint_and_then( cx, LARGE_STACK_ARRAYS, diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index 39b2391c98ec..7fb8e51377a2 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -1,16 +1,22 @@ use super::FOR_KV_MAP; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet_with_applicability, walk_span_to_context}; use clippy_utils::{pat_is_wild, sugg}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::sym; +use rustc_span::{Span, sym}; /// Checks for the `FOR_KV_MAP` lint. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, +) { let pat_span = pat.span; if let PatKind::Tuple(pat, _) = pat.kind @@ -34,21 +40,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx _ => arg, }; - if matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) { + if matches!(ty.opt_diag_name(cx), Some(sym::HashMap | sym::BTreeMap)) + && let Some(arg_span) = walk_span_to_context(arg_span, span.ctxt()) + { span_lint_and_then( cx, FOR_KV_MAP, arg_span, format!("you seem to want to iterate on a map's {kind}s"), |diag| { - let map = sugg::Sugg::hir(cx, arg, "map"); + let mut applicability = Applicability::MachineApplicable; + let map = sugg::Sugg::hir_with_context(cx, arg, span.ctxt(), "map", &mut applicability); + let pat = snippet_with_applicability(cx, new_pat_span, kind, &mut applicability); diag.multipart_suggestion( "use the corresponding method", vec![ - (pat_span, snippet(cx, new_pat_span, kind).into_owned()), + (pat_span, pat.to_string()), (arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_paren())), ], - Applicability::MachineApplicable, + applicability, ); }, ); diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index ddc783069385..83574cab6b67 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -943,7 +943,7 @@ impl Loops { explicit_counter_loop::check(cx, pat, arg, body, expr, label); } self.check_for_loop_arg(cx, pat, arg); - for_kv_map::check(cx, pat, arg, body); + for_kv_map::check(cx, pat, arg, body, span); mut_range_bound::check(cx, arg, body); single_element_loop::check(cx, pat, arg, body, expr); same_item_push::check(cx, pat, arg, body, expr, self.msrv); diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index a037af3433c3..e7b9b1cd3881 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -4,8 +4,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::ForLoop; use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::{snippet, snippet_with_context}; -use clippy_utils::sym; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{contains_return, sym}; use rustc_errors::Applicability; use rustc_hir::{ Block, Closure, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, @@ -82,7 +82,7 @@ pub(super) fn check_iterator_reduction<'tcx>( ) { let closure_body = cx.tcx.hir_body(closure.body).value; let body_ty = cx.typeck_results().expr_ty(closure_body); - if body_ty.is_never() { + if body_ty.is_never() && !contains_return(closure_body) { span_lint_and_then( cx, NEVER_LOOP, diff --git a/clippy_lints/src/manual_ignore_case_cmp.rs b/clippy_lints/src/manual_ignore_case_cmp.rs index 25057b4aeaa2..1c20a8f81efb 100644 --- a/clippy_lints/src/manual_ignore_case_cmp.rs +++ b/clippy_lints/src/manual_ignore_case_cmp.rs @@ -1,7 +1,7 @@ use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sym; use rustc_ast::LitKind; use rustc_errors::Applicability; @@ -111,14 +111,12 @@ impl LateLintPass<'_> for ManualIgnoreCaseCmp { "manual case-insensitive ASCII comparison", |diag| { let mut app = Applicability::MachineApplicable; + let (left_snip, _) = snippet_with_context(cx, left_span, expr.span.ctxt(), "..", &mut app); + let (right_snip, _) = snippet_with_context(cx, right_span, expr.span.ctxt(), "..", &mut app); diag.span_suggestion_verbose( expr.span, "consider using `.eq_ignore_ascii_case()` instead", - format!( - "{neg}{}.eq_ignore_ascii_case({deref}{})", - snippet_with_applicability(cx, left_span, "_", &mut app), - snippet_with_applicability(cx, right_span, "_", &mut app) - ), + format!("{neg}{left_snip}.eq_ignore_ascii_case({deref}{right_snip})"), app, ); }, diff --git a/clippy_lints/src/manual_ilog2.rs b/clippy_lints/src/manual_ilog2.rs index 1c61db530606..4b411a60f3bf 100644 --- a/clippy_lints/src/manual_ilog2.rs +++ b/clippy_lints/src/manual_ilog2.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::{is_from_proc_macro, sym}; use rustc_ast::LitKind; use rustc_data_structures::packed::Pu128; @@ -102,7 +102,7 @@ impl LateLintPass<'_> for ManualIlog2 { fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) { let mut app = Applicability::MachineApplicable; - let recv = snippet_with_applicability(cx, recv.span, "_", &mut app); + let (recv, _) = snippet_with_context(cx, recv.span, full_expr.span.ctxt(), "_", &mut app); span_lint_and_sugg( cx, MANUAL_ILOG2, diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index c35c3d1f62e6..1fc8bb9acce2 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -135,7 +135,7 @@ fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok } else { Applicability::MachineApplicable }; - let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_paren(); + let scrut = Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_paren(); let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee); let (_, _, mutability) = peel_and_count_ty_refs(scrutinee_ty); diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 795355f25f9e..12fe44ef2f13 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -46,6 +46,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: let cast = if input_ty == output_ty { "" } else { ".map(|x| x as _)" }; let mut applicability = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); span_lint_and_then( cx, MATCH_AS_REF, @@ -59,7 +60,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: "use `Option::as_ref()`", format!( "{}.as_ref(){cast}", - Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(), + Sugg::hir_with_context(cx, ex, ctxt, "_", &mut applicability).maybe_paren(), ), applicability, ); @@ -69,7 +70,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: format!("use `Option::{method}()` directly"), format!( "{}.{method}(){cast}", - Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(), + Sugg::hir_with_context(cx, ex, ctxt, "_", &mut applicability).maybe_paren(), ), applicability, ); diff --git a/clippy_lints/src/matches/match_bool.rs b/clippy_lints/src/matches/match_bool.rs index a2c8741f4f74..3e76231b6ef1 100644 --- a/clippy_lints/src/matches/match_bool.rs +++ b/clippy_lints/src/matches/match_bool.rs @@ -16,6 +16,7 @@ pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>] && arms .iter() .all(|arm| arm.pat.walk_short(|p| !matches!(p.kind, PatKind::Binding(..)))) + && arms.len() == 2 { span_lint_and_then( cx, @@ -23,59 +24,58 @@ pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>] expr.span, "`match` on a boolean expression", move |diag| { - if arms.len() == 2 { - let mut app = Applicability::MachineApplicable; - let test_sugg = if let PatKind::Expr(arm_bool) = arms[0].pat.kind { - let test = Sugg::hir_with_applicability(cx, scrutinee, "_", &mut app); - if let PatExprKind::Lit { lit, .. } = arm_bool.kind { - match &lit.node { - LitKind::Bool(true) => Some(test), - LitKind::Bool(false) => Some(!test), - _ => None, - } - .map(|test| { - if let Some(guard) = &arms[0] - .guard - .map(|g| Sugg::hir_with_applicability(cx, g, "_", &mut app)) - { - test.and(guard) - } else { - test - } - }) - } else { - None + let mut app = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); + let test_sugg = if let PatKind::Expr(arm_bool) = arms[0].pat.kind { + let test = Sugg::hir_with_context(cx, scrutinee, ctxt, "_", &mut app); + if let PatExprKind::Lit { lit, .. } = arm_bool.kind { + match &lit.node { + LitKind::Bool(true) => Some(test), + LitKind::Bool(false) => Some(!test), + _ => None, } + .map(|test| { + if let Some(guard) = &arms[0] + .guard + .map(|g| Sugg::hir_with_context(cx, g, ctxt, "_", &mut app)) + { + test.and(guard) + } else { + test + } + }) } else { None + } + } else { + None + }; + + if let Some(test_sugg) = test_sugg { + let ctxt = expr.span.ctxt(); + let (true_expr, false_expr) = (arms[0].body, arms[1].body); + let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { + (false, false) => Some(format!( + "if {} {} else {}", + test_sugg, + expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app), + expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) + )), + (false, true) => Some(format!( + "if {} {}", + test_sugg, + expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app) + )), + (true, false) => Some(format!( + "if {} {}", + !test_sugg, + expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) + )), + (true, true) => None, }; - if let Some(test_sugg) = test_sugg { - let ctxt = expr.span.ctxt(); - let (true_expr, false_expr) = (arms[0].body, arms[1].body); - let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { - (false, false) => Some(format!( - "if {} {} else {}", - test_sugg, - expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app), - expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) - )), - (false, true) => Some(format!( - "if {} {}", - test_sugg, - expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app) - )), - (true, false) => Some(format!( - "if {} {}", - !test_sugg, - expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) - )), - (true, true) => None, - }; - - if let Some(sugg) = sugg { - diag.span_suggestion(expr.span, "consider using an `if`/`else` expression", sugg, app); - } + if let Some(sugg) = sugg { + diag.span_suggestion(expr.span, "consider using an `if`/`else` expression", sugg, app); } } }, diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index bc3783750e5c..bf0c0c4aec3c 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -1,7 +1,6 @@ use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::source::walk_span_to_context; use clippy_utils::sugg::{Sugg, make_unop}; use clippy_utils::ty::needs_ordered_drop; use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr_without_closures}; @@ -25,7 +24,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { .. }) = higher::WhileLet::hir(expr) { - find_method_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false); + find_method_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false, let_span); find_if_let_true(cx, let_pat, let_expr, let_span); } } @@ -39,7 +38,7 @@ pub(super) fn check_if_let<'tcx>( let_span: Span, ) { find_if_let_true(cx, pat, scrutinee, let_span); - find_method_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else); + find_method_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else, let_span); } /// Looks for: @@ -182,6 +181,7 @@ fn find_method_sugg_for_if_let<'tcx>( let_expr: &'tcx Expr<'_>, keyword: &'static str, has_else: bool, + let_span: Span, ) { // also look inside refs // if we have &None for example, peel it so we can detect "if let None = x" @@ -239,15 +239,9 @@ fn find_method_sugg_for_if_let<'tcx>( let expr_span = expr.span; let ctxt = expr.span.ctxt(); - // if/while let ... = ... { ... } - // ^^^ - let Some(res_span) = walk_span_to_context(result_expr.span.source_callsite(), ctxt) else { - return; - }; - // if/while let ... = ... { ... } // ^^^^^^^^^^^^^^^^^^^^^^ - let span = expr_span.until(res_span.shrink_to_hi()); + let span = expr_span.until(let_span.shrink_to_hi()); let mut app = if needs_drop { Applicability::MaybeIncorrect @@ -273,13 +267,14 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op if let Ok(arms) = arms.try_into() // TODO: use `slice::as_array` once stabilized && let Some((good_method, maybe_guard)) = found_good_method(cx, arms) { - let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span)); + let expr_span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span); + let result_expr = match &op.kind { ExprKind::AddrOf(_, _, borrowed) => borrowed, _ => op, }; let mut app = Applicability::MachineApplicable; - let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren(); + let receiver_sugg = Sugg::hir_with_context(cx, result_expr, expr_span.ctxt(), "_", &mut app).maybe_paren(); let mut sugg = format!("{receiver_sugg}.{good_method}"); if let Some(guard) = maybe_guard { @@ -302,14 +297,14 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op return; } - let guard = Sugg::hir(cx, guard, ".."); + let guard = Sugg::hir_with_context(cx, guard, expr_span.ctxt(), "..", &mut app); let _ = write!(sugg, " && {}", guard.maybe_paren()); } span_lint_and_sugg( cx, REDUNDANT_PATTERN_MATCHING, - span, + expr_span, format!("redundant pattern matching, consider using `{good_method}`"), "try", sugg, diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs index 834456ff6668..8135c67d0d78 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints/src/methods/is_empty.rs @@ -3,10 +3,9 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::{is_assert_macro, root_macro_call}; use clippy_utils::res::MaybeResPath; use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context}; -use rustc_hir::{Expr, HirId}; -use rustc_lint::{LateContext, LintContext}; use rustc_hir::attrs::AttributeKind; -use rustc_hir::find_attr; +use rustc_hir::{Expr, HirId, find_attr}; +use rustc_lint::{LateContext, LintContext}; use super::CONST_IS_EMPTY; diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index 16db8663941e..366bfaed73d4 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -2,7 +2,7 @@ use super::ITER_KV_MAP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::{pat_is_wild, sym}; use rustc_hir::{Body, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; @@ -58,6 +58,8 @@ pub(super) fn check<'tcx>( applicability, ); } else { + let (body_snippet, _) = + snippet_with_context(cx, body_expr.span, expr.span.ctxt(), "..", &mut applicability); span_lint_and_sugg( cx, ITER_KV_MAP, @@ -65,9 +67,8 @@ pub(super) fn check<'tcx>( format!("iterating on a map's {replacement_kind}s"), "try", format!( - "{recv_snippet}.{into_prefix}{replacement_kind}s().map(|{}{bound_ident}| {})", + "{recv_snippet}.{into_prefix}{replacement_kind}s().map(|{}{bound_ident}| {body_snippet})", annotation.prefix_str(), - snippet_with_applicability(cx, body_expr.span, "/* body */", &mut applicability) ), applicability, ); diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 9dae6fbb48dd..c3f031edff2e 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,10 +1,10 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs}; use rustc_ast::ast; use rustc_data_structures::packed::Pu128; -use rustc_errors::Applicability; +use rustc_errors::{Applicability, Diag}; use rustc_hir as hir; use rustc_hir::PatKind; use rustc_hir::def::{DefKind, Res}; @@ -59,6 +59,34 @@ struct Replacement { method_name: &'static str, has_args: bool, has_generic_return: bool, + is_short_circuiting: bool, +} + +impl Replacement { + fn default_applicability(&self) -> Applicability { + if self.is_short_circuiting { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + } + } + + fn maybe_add_note(&self, diag: &mut Diag<'_, ()>) { + if self.is_short_circuiting { + diag.note(format!( + "the `{}` method is short circuiting and may change the program semantics if the iterator has side effects", + self.method_name + )); + } + } + + fn maybe_turbofish(&self, ty: Ty<'_>) -> String { + if self.has_generic_return { + format!("::<{ty}>") + } else { + String::new() + } + } } fn check_fold_with_op( @@ -86,32 +114,30 @@ fn check_fold_with_op( && left_expr.res_local_id() == Some(first_arg_id) && (replacement.has_args || right_expr.res_local_id() == Some(second_arg_id)) { - let mut applicability = Applicability::MachineApplicable; - - let turbofish = if replacement.has_generic_return { - format!("::<{}>", cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs()) - } else { - String::new() - }; - - let sugg = if replacement.has_args { - format!( - "{method}{turbofish}(|{second_arg_ident}| {r})", - method = replacement.method_name, - r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), - ) - } else { - format!("{method}{turbofish}()", method = replacement.method_name) - }; - - span_lint_and_sugg( + let span = fold_span.with_hi(expr.span.hi()); + span_lint_and_then( cx, UNNECESSARY_FOLD, - fold_span.with_hi(expr.span.hi()), + span, "this `.fold` can be written more succinctly using another method", - "try", - sugg, - applicability, + |diag| { + let mut applicability = replacement.default_applicability(); + let turbofish = + replacement.maybe_turbofish(cx.typeck_results().expr_ty_adjusted(right_expr).peel_refs()); + let (r_snippet, _) = + snippet_with_context(cx, right_expr.span, expr.span.ctxt(), "EXPR", &mut applicability); + let sugg = if replacement.has_args { + format!( + "{method}{turbofish}(|{second_arg_ident}| {r_snippet})", + method = replacement.method_name, + ) + } else { + format!("{method}{turbofish}()", method = replacement.method_name) + }; + + diag.span_suggestion(span, "try", sugg, applicability); + replacement.maybe_add_note(diag); + }, ); return true; } @@ -131,22 +157,25 @@ fn check_fold_with_method( // Check if the function belongs to the operator && cx.tcx.is_diagnostic_item(method, fn_did) { - let applicability = Applicability::MachineApplicable; - - let turbofish = if replacement.has_generic_return { - format!("::<{}>", cx.typeck_results().expr_ty(expr)) - } else { - String::new() - }; - - span_lint_and_sugg( + let span = fold_span.with_hi(expr.span.hi()); + span_lint_and_then( cx, UNNECESSARY_FOLD, - fold_span.with_hi(expr.span.hi()), + span, "this `.fold` can be written more succinctly using another method", - "try", - format!("{method}{turbofish}()", method = replacement.method_name), - applicability, + |diag| { + diag.span_suggestion( + span, + "try", + format!( + "{method}{turbofish}()", + method = replacement.method_name, + turbofish = replacement.maybe_turbofish(cx.typeck_results().expr_ty(expr)) + ), + replacement.default_applicability(), + ); + replacement.maybe_add_note(diag); + }, ); } } @@ -171,6 +200,7 @@ pub(super) fn check<'tcx>( method_name: "any", has_args: true, has_generic_return: false, + is_short_circuiting: true, }; check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, replacement); }, @@ -179,6 +209,7 @@ pub(super) fn check<'tcx>( method_name: "all", has_args: true, has_generic_return: false, + is_short_circuiting: true, }; check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, replacement); }, @@ -187,6 +218,7 @@ pub(super) fn check<'tcx>( method_name: "sum", has_args: false, has_generic_return: needs_turbofish(cx, expr), + is_short_circuiting: false, }; if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, replacement) { check_fold_with_method(cx, expr, acc, fold_span, sym::add, replacement); @@ -197,6 +229,7 @@ pub(super) fn check<'tcx>( method_name: "product", has_args: false, has_generic_return: needs_turbofish(cx, expr), + is_short_circuiting: false, }; if !check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, replacement) { check_fold_with_method(cx, expr, acc, fold_span, sym::mul, replacement); diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index a6a39cb6ab30..74e8dbc15a6c 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -3,7 +3,7 @@ use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_context}; use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_and_count_ty_refs}; use clippy_utils::visitors::find_all_ret_expressions; use clippy_utils::{fn_def_id, get_parent_expr, is_expr_temporary_value, return_ty, sym}; @@ -131,8 +131,10 @@ fn check_addr_of_expr( && (*referent_ty != receiver_ty || (matches!(referent_ty.kind(), ty::Array(..)) && is_copy(cx, *referent_ty)) || is_cow_into_owned(cx, method_name, method_parent_id)) - && let Some(receiver_snippet) = receiver.span.get_source_text(cx) { + let mut applicability = Applicability::MachineApplicable; + let (receiver_snippet, _) = snippet_with_context(cx, receiver.span, expr.span.ctxt(), "..", &mut applicability); + if receiver_ty == target_ty && n_target_refs >= n_receiver_refs { span_lint_and_sugg( cx, @@ -145,7 +147,7 @@ fn check_addr_of_expr( "", width = n_target_refs - n_receiver_refs ), - Applicability::MachineApplicable, + applicability, ); return true; } @@ -165,8 +167,8 @@ fn check_addr_of_expr( parent.span, format!("unnecessary use of `{method_name}`"), "use", - receiver_snippet.to_owned(), - Applicability::MachineApplicable, + receiver_snippet.to_string(), + applicability, ); } else { span_lint_and_sugg( @@ -176,7 +178,7 @@ fn check_addr_of_expr( format!("unnecessary use of `{method_name}`"), "remove this", String::new(), - Applicability::MachineApplicable, + applicability, ); } return true; @@ -191,7 +193,7 @@ fn check_addr_of_expr( format!("unnecessary use of `{method_name}`"), "use", format!("{receiver_snippet}.as_ref()"), - Applicability::MachineApplicable, + applicability, ); return true; } @@ -409,8 +411,10 @@ fn check_other_call_arg<'tcx>( None } && can_change_type(cx, maybe_arg, receiver_ty) - && let Some(receiver_snippet) = receiver.span.get_source_text(cx) { + let mut applicability = Applicability::MachineApplicable; + let (receiver_snippet, _) = snippet_with_context(cx, receiver.span, expr.span.ctxt(), "..", &mut applicability); + span_lint_and_sugg( cx, UNNECESSARY_TO_OWNED, @@ -418,7 +422,7 @@ fn check_other_call_arg<'tcx>( format!("unnecessary use of `{method_name}`"), "use", format!("{:&>n_refs$}{receiver_snippet}", ""), - Applicability::MachineApplicable, + applicability, ); return true; } diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs index eeea6dfd5f4b..5dd38cf059c2 100644 --- a/clippy_lints/src/missing_enforced_import_rename.rs +++ b/clippy_lints/src/missing_enforced_import_rename.rs @@ -82,7 +82,8 @@ impl LateLintPass<'_> for ImportRename { && let Some(import) = match snip.split_once(" as ") { None => Some(snip.as_str()), Some((import, rename)) => { - if rename.trim() == name.as_str() { + let trimmed_rename = rename.trim(); + if trimmed_rename == "_" || trimmed_rename == name.as_str() { None } else { Some(import.trim()) diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs index 741f38f97560..5b6b4f112455 100644 --- a/clippy_lints/src/multiple_bound_locations.rs +++ b/clippy_lints/src/multiple_bound_locations.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.78.0"] pub MULTIPLE_BOUND_LOCATIONS, - suspicious, + style, "defining generic bounds in multiple locations" } diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 2fef8404f824..ad44d65b4d66 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -143,7 +143,7 @@ fn check_expr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, ty_ascription: &T && new.ident.name == sym::new { let mut applicability = Applicability::MaybeIncorrect; - let arg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability); + let arg = Sugg::hir_with_context(cx, arg, expr.span.ctxt(), "_", &mut applicability); let mut suggs = vec![(expr.span, format!("std::sync::atomic::{atomic_name}::new({arg})"))]; match ty_ascription { TypeAscriptionKind::Required(ty_ascription) => { diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 854e927aa2f7..6b5db9dcf3e2 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; use clippy_utils::{ SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call, @@ -171,8 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { && SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b) { let mut applicability = Applicability::MachineApplicable; - let cond = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability); - let lhs = snippet_with_applicability(cx, lhs_a.span, "..", &mut applicability); + let cond = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "..", &mut applicability); + let (lhs, _) = snippet_with_context(cx, lhs_a.span, e.span.ctxt(), "..", &mut applicability); let mut sugg = if a == b { format!("{cond}; {lhs} = {a:?};") } else { diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index d03188f1d39b..55a5a16c0099 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -56,8 +56,20 @@ declare_lint_pass!(NeedlessForEach => [NEEDLESS_FOR_EACH]); impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind - && let ExprKind::MethodCall(method_name, for_each_recv, [for_each_arg], _) = expr.kind + if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind { + check_expr(cx, expr, stmt.span); + } + } + + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + if let Some(expr) = block.expr { + check_expr(cx, expr, expr.span); + } + } +} + +fn check_expr(cx: &LateContext<'_>, expr: &Expr<'_>, outer_span: Span) { + if let ExprKind::MethodCall(method_name, for_each_recv, [for_each_arg], _) = expr.kind && let ExprKind::MethodCall(_, iter_recv, [], _) = for_each_recv.kind // Skip the lint if the call chain is too long. e.g. `v.field.iter().for_each()` or // `v.foo().iter().for_each()` must be skipped. @@ -76,69 +88,74 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { // Skip the lint if the body is not safe, so as not to suggest `for … in … unsafe {}` // and suggesting `for … in … { unsafe { } }` is a little ugly. && !matches!(body.value.kind, ExprKind::Block(Block { rules: BlockCheckMode::UnsafeBlock(_), .. }, ..)) + { + let mut applicability = Applicability::MachineApplicable; + + // If any closure parameter has an explicit type specified, applying the lint would necessarily + // remove that specification, possibly breaking type inference + if fn_decl + .inputs + .iter() + .any(|input| matches!(input.kind, TyKind::Infer(..))) { - let mut applicability = Applicability::MachineApplicable; + applicability = Applicability::MaybeIncorrect; + } - // If any closure parameter has an explicit type specified, applying the lint would necessarily - // remove that specification, possibly breaking type inference - if fn_decl - .inputs - .iter() - .any(|input| matches!(input.kind, TyKind::Infer(..))) - { - applicability = Applicability::MaybeIncorrect; - } + let mut ret_collector = RetCollector::default(); + ret_collector.visit_expr(body.value); - let mut ret_collector = RetCollector::default(); - ret_collector.visit_expr(body.value); + // Skip the lint if `return` is used in `Loop` in order not to suggest using `'label`. + if ret_collector.ret_in_loop { + return; + } - // Skip the lint if `return` is used in `Loop` in order not to suggest using `'label`. - if ret_collector.ret_in_loop { - return; - } + let ret_suggs = if ret_collector.spans.is_empty() { + None + } else { + applicability = Applicability::MaybeIncorrect; + Some( + ret_collector + .spans + .into_iter() + .map(|span| (span, "continue".to_string())) + .collect(), + ) + }; - let ret_suggs = if ret_collector.spans.is_empty() { - None + let body_param_sugg = snippet_with_applicability(cx, body.params[0].pat.span, "..", &mut applicability); + let for_each_rev_sugg = snippet_with_applicability(cx, for_each_recv.span, "..", &mut applicability); + let (body_value_sugg, is_macro_call) = + snippet_with_context(cx, body.value.span, for_each_recv.span.ctxt(), "..", &mut applicability); + + let sugg = format!( + "for {} in {} {}", + body_param_sugg, + for_each_rev_sugg, + if is_macro_call { + format!("{{ {body_value_sugg}; }}") } else { - applicability = Applicability::MaybeIncorrect; - Some( - ret_collector - .spans - .into_iter() - .map(|span| (span, "continue".to_string())) - .collect(), - ) - }; - - let body_param_sugg = snippet_with_applicability(cx, body.params[0].pat.span, "..", &mut applicability); - let for_each_rev_sugg = snippet_with_applicability(cx, for_each_recv.span, "..", &mut applicability); - let (body_value_sugg, is_macro_call) = - snippet_with_context(cx, body.value.span, for_each_recv.span.ctxt(), "..", &mut applicability); - - let sugg = format!( - "for {} in {} {}", - body_param_sugg, - for_each_rev_sugg, - if is_macro_call { - format!("{{ {body_value_sugg}; }}") - } else { - match body.value.kind { - ExprKind::Block(block, _) if is_let_desugar(block) => { - format!("{{ {body_value_sugg} }}") - }, - ExprKind::Block(_, _) => body_value_sugg.to_string(), - _ => format!("{{ {body_value_sugg}; }}"), - } + match body.value.kind { + ExprKind::Block(block, _) if is_let_desugar(block) => { + format!("{{ {body_value_sugg} }}") + }, + ExprKind::Block(_, _) => body_value_sugg.to_string(), + _ => format!("{{ {body_value_sugg}; }}"), } - ); + } + ); - span_lint_and_then(cx, NEEDLESS_FOR_EACH, stmt.span, "needless use of `for_each`", |diag| { - diag.span_suggestion(stmt.span, "try", sugg, applicability); + span_lint_and_then( + cx, + NEEDLESS_FOR_EACH, + outer_span, + "needless use of `for_each`", + |diag| { + diag.span_suggestion(outer_span, "try", sugg, applicability); if let Some(ret_suggs) = ret_suggs { diag.multipart_suggestion("...and replace `return` with `continue`", ret_suggs, applicability); } - }); - } + }, + ); } } diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 67493d54b552..e151c06c6c20 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -1,16 +1,15 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::return_ty; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability}; use clippy_utils::sugg::DiagExt; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::HirIdSet; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::{Attribute, HirIdSet}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocKind; use rustc_session::impl_lint_pass; use rustc_span::sym; -use rustc_hir::Attribute; -use rustc_hir::attrs::AttributeKind; declare_clippy_lint! { /// ### What it does @@ -59,6 +58,7 @@ pub struct NewWithoutDefault { impl_lint_pass!(NewWithoutDefault => [NEW_WITHOUT_DEFAULT]); impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { + #[expect(clippy::too_many_lines)] fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { let hir::ItemKind::Impl(hir::Impl { of_trait: None, @@ -139,16 +139,34 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { sugg.push_str(&snippet_with_applicability(cx.sess(), *attr_span, "_", &mut app)); sugg.push('\n'); } - } sugg }; let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app); let where_clause_sugg = if generics.has_where_clause_predicates { - format!( - "\n{}\n", - snippet_with_applicability(cx, generics.where_clause_span, "", &mut app) - ) + let where_clause_sugg = + snippet_with_applicability(cx, generics.where_clause_span, "", &mut app).to_string(); + let mut where_clause_sugg = reindent_multiline(&where_clause_sugg, true, Some(4)); + if impl_item.generics.has_where_clause_predicates { + if !where_clause_sugg.ends_with(',') { + where_clause_sugg.push(','); + } + + let additional_where_preds = + snippet_with_applicability(cx, impl_item.generics.where_clause_span, "", &mut app); + let ident = indent_of(cx, generics.where_clause_span).unwrap_or(0); + // Remove the leading `where ` keyword + let additional_where_preds = additional_where_preds.trim_start_matches("where").trim_start(); + where_clause_sugg.push('\n'); + where_clause_sugg.extend(std::iter::repeat_n(' ', ident)); + where_clause_sugg.push_str(additional_where_preds); + } + format!("\n{where_clause_sugg}\n") + } else if impl_item.generics.has_where_clause_predicates { + let where_clause_sugg = + snippet_with_applicability(cx, impl_item.generics.where_clause_span, "", &mut app); + let where_clause_sugg = reindent_multiline(&where_clause_sugg, true, Some(4)); + format!("\n{}\n", where_clause_sugg.trim_start()) } else { String::new() }; diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index 05358de5b348..39097833a6c5 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeQPath; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; @@ -94,51 +94,37 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) return; } - let arg_snip = snippet(cx, arg_span, ".."); - let expr_snip; - let eq_impl; - if with_deref.is_implemented() && !arg_ty.peel_refs().is_str() { - expr_snip = format!("*{arg_snip}"); - eq_impl = with_deref; + let mut applicability = Applicability::MachineApplicable; + let (arg_snip, _) = snippet_with_context(cx, arg_span, expr.span.ctxt(), "..", &mut applicability); + let (expr_snip, eq_impl) = if with_deref.is_implemented() && !arg_ty.peel_refs().is_str() { + (format!("*{arg_snip}"), with_deref) } else { - expr_snip = arg_snip.to_string(); - eq_impl = without_deref; - } + (arg_snip.to_string(), without_deref) + }; - let span; - let hint; - if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { - span = expr.span; - hint = expr_snip; + let (span, hint) = if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { + (expr.span, expr_snip) } else { - span = expr.span.to(other.span); + let span = expr.span.to(other.span); let cmp_span = if other.span < expr.span { other.span.between(expr.span) } else { expr.span.between(other.span) }; - if eq_impl.ty_eq_other { - hint = format!( - "{expr_snip}{}{}", - snippet(cx, cmp_span, ".."), - snippet(cx, other.span, "..") - ); - } else { - hint = format!( - "{}{}{expr_snip}", - snippet(cx, other.span, ".."), - snippet(cx, cmp_span, "..") - ); - } - } - diag.span_suggestion( - span, - "try", - hint, - Applicability::MachineApplicable, // snippet - ); + let (cmp_snippet, _) = snippet_with_context(cx, cmp_span, expr.span.ctxt(), "..", &mut applicability); + let (other_snippet, _) = + snippet_with_context(cx, other.span, expr.span.ctxt(), "..", &mut applicability); + + if eq_impl.ty_eq_other { + (span, format!("{expr_snip}{cmp_snippet}{other_snippet}")) + } else { + (span, format!("{other_snippet}{cmp_snippet}{expr_snip}")) + } + }; + + diag.span_suggestion(span, "try", hint, applicability); }, ); } diff --git a/clippy_lints/src/operators/manual_div_ceil.rs b/clippy_lints/src/operators/manual_div_ceil.rs index 98aa47421537..5ed923d719bc 100644 --- a/clippy_lints/src/operators/manual_div_ceil.rs +++ b/clippy_lints/src/operators/manual_div_ceil.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::sugg::{Sugg, has_enclosing_paren}; use clippy_utils::{SpanlessEq, sym}; use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp}; @@ -7,7 +8,7 @@ use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self}; +use rustc_middle::ty::{self, Ty}; use rustc_span::source_map::Spanned; use super::MANUAL_DIV_CEIL; @@ -16,59 +17,84 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: & let mut applicability = Applicability::MachineApplicable; if op == BinOpKind::Div - && check_int_ty_and_feature(cx, lhs) - && check_int_ty_and_feature(cx, rhs) - && let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = lhs.kind + && check_int_ty_and_feature(cx, cx.typeck_results().expr_ty(lhs)) + && check_int_ty_and_feature(cx, cx.typeck_results().expr_ty(rhs)) && msrv.meets(cx, msrvs::DIV_CEIL) { - // (x + (y - 1)) / y - if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind - && inner_op.node == BinOpKind::Add - && sub_op.node == BinOpKind::Sub - && check_literal(sub_rhs) - && check_eq_expr(cx, sub_lhs, rhs) - { - build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); - return; - } + match lhs.kind { + ExprKind::Binary(inner_op, inner_lhs, inner_rhs) => { + // (x + (y - 1)) / y + if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind + && inner_op.node == BinOpKind::Add + && sub_op.node == BinOpKind::Sub + && check_literal(sub_rhs) + && check_eq_expr(cx, sub_lhs, rhs) + { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + return; + } - // ((y - 1) + x) / y - if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind - && inner_op.node == BinOpKind::Add - && sub_op.node == BinOpKind::Sub - && check_literal(sub_rhs) - && check_eq_expr(cx, sub_lhs, rhs) - { - build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); - return; - } + // ((y - 1) + x) / y + if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind + && inner_op.node == BinOpKind::Add + && sub_op.node == BinOpKind::Sub + && check_literal(sub_rhs) + && check_eq_expr(cx, sub_lhs, rhs) + { + build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); + return; + } - // (x + y - 1) / y - if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind - && inner_op.node == BinOpKind::Sub - && add_op.node == BinOpKind::Add - && check_literal(inner_rhs) - && check_eq_expr(cx, add_rhs, rhs) - { - build_suggestion(cx, expr, add_lhs, rhs, &mut applicability); - } + // (x + y - 1) / y + if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind + && inner_op.node == BinOpKind::Sub + && add_op.node == BinOpKind::Add + && check_literal(inner_rhs) + && check_eq_expr(cx, add_rhs, rhs) + { + build_suggestion(cx, expr, add_lhs, rhs, &mut applicability); + } - // (x + (Y - 1)) / Y - if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, rhs) { - build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); - } + // (x + (Y - 1)) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, rhs) { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + } - // ((Y - 1) + x) / Y - if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, rhs) { - build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); - } + // ((Y - 1) + x) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, rhs) { + build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability); + } - // (x - (-Y - 1)) / Y - if inner_op.node == BinOpKind::Sub - && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = rhs.kind - && differ_by_one(abs_div_rhs, inner_rhs) - { - build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + // (x - (-Y - 1)) / Y + if inner_op.node == BinOpKind::Sub + && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = rhs.kind + && differ_by_one(abs_div_rhs, inner_rhs) + { + build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability); + } + }, + ExprKind::MethodCall(method, receiver, [next_multiple_of_arg], _) => { + // x.next_multiple_of(Y) / Y + if method.ident.name == sym::next_multiple_of + && check_int_ty(cx.typeck_results().expr_ty(receiver)) + && check_eq_expr(cx, next_multiple_of_arg, rhs) + { + build_suggestion(cx, expr, receiver, rhs, &mut applicability); + } + }, + ExprKind::Call(callee, [receiver, next_multiple_of_arg]) => { + // int_type::next_multiple_of(x, Y) / Y + if let Some(impl_ty_binder) = callee + .ty_rel_def_if_named(cx, sym::next_multiple_of) + .assoc_fn_parent(cx) + .opt_impl_ty(cx) + && check_int_ty(impl_ty_binder.skip_binder()) + && check_eq_expr(cx, next_multiple_of_arg, rhs) + { + build_suggestion(cx, expr, receiver, rhs, &mut applicability); + } + }, + _ => (), } } } @@ -91,8 +117,11 @@ fn differ_by_one(small_expr: &Expr<'_>, large_expr: &Expr<'_>) -> bool { } } -fn check_int_ty_and_feature(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expr); +fn check_int_ty(expr_ty: Ty<'_>) -> bool { + matches!(expr_ty.peel_refs().kind(), ty::Int(_) | ty::Uint(_)) +} + +fn check_int_ty_and_feature(cx: &LateContext<'_>, expr_ty: Ty<'_>) -> bool { match expr_ty.peel_refs().kind() { ty::Uint(_) => true, ty::Int(_) => cx.tcx.features().enabled(sym::int_roundings), diff --git a/clippy_lints/src/operators/manual_is_multiple_of.rs b/clippy_lints/src/operators/manual_is_multiple_of.rs index 0b9bd4fb6d32..291d81097b51 100644 --- a/clippy_lints/src/operators/manual_is_multiple_of.rs +++ b/clippy_lints/src/operators/manual_is_multiple_of.rs @@ -35,7 +35,7 @@ pub(super) fn check<'tcx>( { let mut app = Applicability::MachineApplicable; let divisor = deref_sugg( - Sugg::hir_with_applicability(cx, operand_right, "_", &mut app), + Sugg::hir_with_context(cx, operand_right, expr.span.ctxt(), "_", &mut app), cx.typeck_results().expr_ty_adjusted(operand_right), ); span_lint_and_sugg( @@ -47,7 +47,7 @@ pub(super) fn check<'tcx>( format!( "{}{}.is_multiple_of({divisor})", if op == BinOpKind::Eq { "" } else { "!" }, - Sugg::hir_with_applicability(cx, operand_left, "_", &mut app).maybe_paren() + Sugg::hir_with_context(cx, operand_left, expr.span.ctxt(), "_", &mut app).maybe_paren() ), app, ); diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 59d31f782bc3..e5fb3c0fa431 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -5,7 +5,7 @@ use clippy_config::types::MatchLintBehaviour; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::usage::local_used_after_expr; @@ -147,7 +147,8 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { && !span_contains_cfg(cx, els.span) { let mut applicability = Applicability::MaybeIncorrect; - let init_expr_str = Sugg::hir_with_applicability(cx, init_expr, "..", &mut applicability).maybe_paren(); + let init_expr_str = + Sugg::hir_with_context(cx, init_expr, stmt.span.ctxt(), "..", &mut applicability).maybe_paren(); // Take care when binding is `ref` let sugg = if let PatKind::Binding( BindingMode(ByRef::Yes(_, ref_mutability), binding_mutability), @@ -295,7 +296,7 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex && (is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block)) { let mut applicability = Applicability::MachineApplicable; - let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability); + let receiver_str = snippet_with_context(cx, caller.span, expr.span.ctxt(), "..", &mut applicability).0; let by_ref = !cx.type_is_copy_modulo_regions(caller_ty) && !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)); let sugg = if let Some(else_inner) = r#else { diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs index 92d1b112198f..e4906eb0c777 100644 --- a/clippy_lints/src/single_range_in_vec_init.rs +++ b/clippy_lints/src/single_range_in_vec_init.rs @@ -98,7 +98,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit { && snippet.starts_with(suggested_type.starts_with()) && snippet.ends_with(suggested_type.ends_with()) { - let mut applicability = Applicability::MachineApplicable; + let mut applicability = Applicability::MaybeIncorrect; let (start_snippet, _) = snippet_with_context(cx, start.expr.span, span.ctxt(), "..", &mut applicability); let (end_snippet, _) = snippet_with_context(cx, end.expr.span, span.ctxt(), "..", &mut applicability); diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 1d0efa46a14c..c0be724bcdee 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::res::{MaybeDef, MaybeQPath}; -use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context}; use clippy_utils::{ SpanlessEq, get_expr_use_or_unification_node, get_parent_expr, is_lint_allowed, method_calls, peel_blocks, sym, }; @@ -273,6 +273,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { let string_expression = &expressions[0].0; let snippet_app = snippet_with_applicability(cx, string_expression.span, "..", &mut applicability); + let (right_snip, _) = snippet_with_context(cx, right.span, e.span.ctxt(), "..", &mut applicability); span_lint_and_sugg( cx, @@ -280,7 +281,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { e.span, "calling a slice of `as_bytes()` with `from_utf8` should be not necessary", "try", - format!("Some(&{snippet_app}[{}])", snippet(cx, right.span, "..")), + format!("Some(&{snippet_app}[{right_snip}])"), applicability, ); } @@ -404,7 +405,8 @@ impl<'tcx> LateLintPass<'tcx> for StrToString { "`to_string()` called on a `&str`", |diag| { let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, self_arg.span, "..", &mut applicability); + let (snippet, _) = + snippet_with_context(cx, self_arg.span, expr.span.ctxt(), "..", &mut applicability); diag.span_suggestion(expr.span, "try", format!("{snippet}.to_owned()"), applicability); }, ); diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index 58d692db5029..0d50bd547652 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead. /// /// ### Why is this bad? - /// This avoids calling an unsafe `libc` function. - /// Currently, it also avoids calculating the length. + /// libc::strlen is an unsafe function, which we don't need to call + /// if all we want to know is the length of the c-string. /// /// ### Example /// ```rust, ignore diff --git a/clippy_lints/src/transmute/transmuting_null.rs b/clippy_lints/src/transmute/transmuting_null.rs index 31e770f421e1..3f435f255d91 100644 --- a/clippy_lints/src/transmute/transmuting_null.rs +++ b/clippy_lints/src/transmute/transmuting_null.rs @@ -2,7 +2,7 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::is_integer_const; use clippy_utils::res::{MaybeDef, MaybeResPath}; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{ConstBlock, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::symbol::sym; @@ -42,5 +42,23 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t return true; } + // Catching: + // `std::mem::transmute({ 0 as *const u64 })` and similar const blocks + if let ExprKind::Block(block, _) = arg.kind + && block.stmts.is_empty() + && let Some(inner) = block.expr + { + // Run again with the inner expression + return check(cx, expr, inner, to_ty); + } + + // Catching: + // `std::mem::transmute(const { u64::MIN as *const u64 });` + if let ExprKind::ConstBlock(ConstBlock { body, .. }) = arg.kind { + // Strip out the const and run again + let block = cx.tcx.hir_body(body).value; + return check(cx, expr, block, to_ty); + } + false } diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index c06313d1a4c4..423301edfe83 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -456,13 +456,25 @@ fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) fn adjustments(cx: &LateContext<'_>, expr: &Expr<'_>) -> String { let mut prefix = String::new(); - for adj in cx.typeck_results().expr_adjustments(expr) { + + let adjustments = cx.typeck_results().expr_adjustments(expr); + + let [.., last] = adjustments else { return prefix }; + let target = last.target; + + for adj in adjustments { match adj.kind { Adjust::Deref(_) => prefix = format!("*{prefix}"), Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Mut { .. })) => prefix = format!("&mut {prefix}"), Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Not)) => prefix = format!("&{prefix}"), _ => {}, } + + // Stop once we reach the final target type. + // This prevents over-adjusting (e.g. suggesting &**y instead of *y). + if adj.target == target { + break; + } } prefix } diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 7a54ba7a8fe1..58b153f06545 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -321,7 +321,6 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { }, ConstArgKind::Struct(..) => chain!(self, "let ConstArgKind::Struct(..) = {const_arg}.kind"), ConstArgKind::TupleCall(..) => chain!(self, "let ConstArgKind::TupleCall(..) = {const_arg}.kind"), - ConstArgKind::Tup(..) => chain!(self, "let ConstArgKind::Tup(..) = {const_arg}.kind"), ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"), ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"), ConstArgKind::Literal(..) => chain!(self, "let ConstArgKind::Literal(..) = {const_arg}.kind") diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs index fda65bc84eda..f56b6f31550b 100644 --- a/clippy_lints_internal/src/lint_without_lint_pass.rs +++ b/clippy_lints_internal/src/lint_without_lint_pass.rs @@ -250,8 +250,8 @@ pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item< if let hir::Attribute::Unparsed(attr_kind) = &attr // Identify attribute && let [tool_name, attr_name] = &attr_kind.path.segments[..] - && tool_name.name == sym::clippy - && attr_name.name == sym::version + && tool_name == &sym::clippy + && attr_name == &sym::version && let Some(version) = attr.value_str() { Some(version) diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 01257c1a3059..ecd36b157571 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-12-25 +nightly-2026-01-08 ``` @@ -30,7 +30,7 @@ Function signatures can change or be removed without replacement without any pri -Copyright 2014-2025 The Rust Project Developers +Copyright (c) The Rust Project Contributors Licensed under the Apache License, Version 2.0 <[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 618719286e8f..cf8716398efb 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -976,7 +976,9 @@ pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool { l.style == r.style && match (&l.kind, &r.kind) { (DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2, - (Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_attr_item_kind(&l.item.args, &r.item.args), + (Normal(l), Normal(r)) => { + eq_path(&l.item.path, &r.item.path) && eq_attr_item_kind(&l.item.args, &r.item.args) + }, _ => false, } } diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 87fdd755908b..32f6cb4fd5e9 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -23,7 +23,9 @@ pub fn get_builtin_attr<'a, A: AttributeExt + 'a>( if let [clippy, segment2] = &*attr.path() && *clippy == sym::clippy { - let path_span = attr.path_span().expect("Clippy attributes are unparsed and have a span"); + let path_span = attr + .path_span() + .expect("Clippy attributes are unparsed and have a span"); let new_name = match *segment2 { sym::cyclomatic_complexity => Some("cognitive_complexity"), sym::author @@ -48,7 +50,7 @@ pub fn get_builtin_attr<'a, A: AttributeExt + 'a>( .with_span_suggestion( path_span, "consider using", - format!("clippy::{}", new_name), + format!("clippy::{new_name}"), Applicability::MachineApplicable, ) .emit(); diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 5f4b87590dc1..334cc6bb5d55 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1140,9 +1140,11 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), - ConstArgKind::Struct(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Tup(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) | ConstArgKind::Literal(..) => { - None - }, + ConstArgKind::Struct(..) + | ConstArgKind::TupleCall(..) + | ConstArgKind::Path(_) + | ConstArgKind::Error(..) + | ConstArgKind::Infer(..) => None, }, } } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index b4e483ea8072..73b1cbb21548 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -675,7 +675,7 @@ impl HirEqInterExpr<'_, '_, '_> { .iter() .zip(*inits_b) .all(|(init_a, init_b)| self.eq_const_arg(init_a.expr, init_b.expr)) - } + }, (ConstArgKind::TupleCall(path_a, args_a), ConstArgKind::TupleCall(path_b, args_b)) => { self.eq_qpath(path_a, path_b) && args_a @@ -688,7 +688,7 @@ impl HirEqInterExpr<'_, '_, '_> { .iter() .zip(*args_b) .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) - } + }, (ConstArgKind::Literal(kind_l), ConstArgKind::Literal(kind_r)) => { kind_l == kind_r }, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 38e1542cd758..2a620e917228 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -90,13 +90,13 @@ use std::sync::{Mutex, MutexGuard, OnceLock}; use itertools::Itertools; use rustc_abi::Integer; use rustc_ast::ast::{self, LitKind, RangeLimits}; -use rustc_ast::join_path_syms; +use rustc_ast::{LitIntType, join_path_syms}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::indexmap; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, CfgEntry}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::definitions::{DefPath, DefPathData}; @@ -121,7 +121,6 @@ use rustc_middle::ty::{ self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt, TypeFlags, TypeVisitableExt, UintTy, UpvarCapture, }; -use rustc_hir::attrs::CfgEntry; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; @@ -1386,6 +1385,17 @@ pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool { false } +/// Checks whether the given expression is an untyped integer literal. +pub fn is_integer_literal_untyped(expr: &Expr<'_>) -> bool { + if let ExprKind::Lit(spanned) = expr.kind + && let LitKind::Int(_, suffix) = spanned.node + { + return suffix == LitIntType::Unsuffixed; + } + + false +} + /// Checks whether the given expression is a constant literal of the given value. pub fn is_float_literal(expr: &Expr<'_>, value: f64) -> bool { if let ExprKind::Lit(spanned) = expr.kind @@ -2403,7 +2413,10 @@ pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool { /// use [`is_in_cfg_test`] pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool { if let Some(cfgs) = find_attr!(tcx.hir_attrs(id), AttributeKind::CfgTrace(cfgs) => cfgs) - && cfgs.iter().any(|(cfg, _)| { matches!(cfg, CfgEntry::NameValue { name: sym::test, ..})}) { + && cfgs + .iter() + .any(|(cfg, _)| matches!(cfg, CfgEntry::NameValue { name: sym::test, .. })) + { true } else { false @@ -2423,9 +2436,11 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { /// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied. pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { find_attr!(tcx.get_all_attrs(def_id), AttributeKind::CfgTrace(..)) - || find_attr!(tcx - .hir_parent_iter(tcx.local_def_id_to_hir_id(def_id)) - .flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)), AttributeKind::CfgTrace(..)) + || find_attr!( + tcx.hir_parent_iter(tcx.local_def_id_to_hir_id(def_id)) + .flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)), + AttributeKind::CfgTrace(..) + ) } /// Walks up the HIR tree from the given expression in an attempt to find where the value is diff --git a/clippy_utils/src/res.rs b/clippy_utils/src/res.rs index a3efece7d224..e890a73620a5 100644 --- a/clippy_utils/src/res.rs +++ b/clippy_utils/src/res.rs @@ -301,7 +301,7 @@ impl<'tcx> MaybeQPath<'tcx> for &'tcx PatExpr<'_> { fn opt_qpath(self) -> Option> { match &self.kind { PatExprKind::Path(qpath) => Some((qpath, self.hir_id)), - _ => None, + PatExprKind::Lit { .. } => None, } } } @@ -419,7 +419,7 @@ impl<'a> MaybeResPath<'a> for &PatExpr<'a> { fn opt_res_path(self) -> OptResPath<'a> { match &self.kind { PatExprKind::Path(qpath) => qpath.opt_res_path(), - _ => (None, None), + PatExprKind::Lit { .. } => (None, None), } } } diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 2ef2afb45071..3ade38bea8ed 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -127,7 +127,7 @@ impl<'a> Sugg<'a> { /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*` /// function variants of `Sugg`, since these use different snippet functions. - fn hir_from_snippet( + pub fn hir_from_snippet( cx: &LateContext<'_>, expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>, diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index a0d2e8673fe6..5357a0941d32 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -248,6 +248,7 @@ generate! { next_back, next_if, next_if_eq, + next_multiple_of, next_tuple, nth, ok, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index dbec79e111fb..0755e1d29c69 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-12-25" +channel = "nightly-2026-01-08" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/rustc_tools_util/README.md b/rustc_tools_util/README.md index f47a4c69c2c3..45d2844ad00b 100644 --- a/rustc_tools_util/README.md +++ b/rustc_tools_util/README.md @@ -51,7 +51,7 @@ The changelog for `rustc_tools_util` is available under: -Copyright 2014-2025 The Rust Project Developers +Copyright (c) The Rust Project Contributors Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/tests/ui-toml/missing_enforced_import_rename/clippy.toml b/tests/ui-toml/missing_enforced_import_rename/clippy.toml index 05ba822874d5..11af5b0a3306 100644 --- a/tests/ui-toml/missing_enforced_import_rename/clippy.toml +++ b/tests/ui-toml/missing_enforced_import_rename/clippy.toml @@ -6,5 +6,6 @@ enforced-import-renames = [ { path = "std::clone", rename = "foo" }, { path = "std::thread::sleep", rename = "thread_sleep" }, { path = "std::any::type_name", rename = "ident" }, - { path = "std::sync::Mutex", rename = "StdMutie" } + { path = "std::sync::Mutex", rename = "StdMutie" }, + { path = "std::io::Write", rename = "to_test_rename_as_underscore" } ] diff --git a/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.fixed b/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.fixed index 3e882f496985..c96df2884b8d 100644 --- a/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.fixed +++ b/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.fixed @@ -15,6 +15,7 @@ use std::{ sync :: Mutex as StdMutie, //~^ missing_enforced_import_renames }; +use std::io::Write as _; fn main() { use std::collections::BTreeMap as Map; diff --git a/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs b/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs index 32255af5117f..662e89157675 100644 --- a/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs +++ b/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs @@ -15,6 +15,7 @@ use std::{ sync :: Mutex, //~^ missing_enforced_import_renames }; +use std::io::Write as _; fn main() { use std::collections::BTreeMap as OopsWrongRename; diff --git a/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.stderr b/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.stderr index 982b144eb872..139331d17619 100644 --- a/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.stderr +++ b/tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.stderr @@ -32,7 +32,7 @@ LL | sync :: Mutex, | ^^^^^^^^^^^^^ help: try: `sync :: Mutex as StdMutie` error: this import should be renamed - --> tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs:20:5 + --> tests/ui-toml/missing_enforced_import_rename/conf_missing_enforced_import_rename.rs:21:5 | LL | use std::collections::BTreeMap as OopsWrongRename; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `use std::collections::BTreeMap as Map` diff --git a/tests/ui/bool_assert_comparison.fixed b/tests/ui/bool_assert_comparison.fixed index ec76abbef05a..cd390ce0db9d 100644 --- a/tests/ui/bool_assert_comparison.fixed +++ b/tests/ui/bool_assert_comparison.fixed @@ -216,3 +216,16 @@ fn main() { assert!(!(b + b)); //~^ bool_assert_comparison } + +fn issue16279() { + macro_rules! is_empty { + ($x:expr) => { + $x.is_empty() + }; + } + + assert!(!is_empty!("a")); + //~^ bool_assert_comparison + assert!(is_empty!("")); + //~^ bool_assert_comparison +} diff --git a/tests/ui/bool_assert_comparison.rs b/tests/ui/bool_assert_comparison.rs index 40824a23c82e..b2ea5b6ea540 100644 --- a/tests/ui/bool_assert_comparison.rs +++ b/tests/ui/bool_assert_comparison.rs @@ -216,3 +216,16 @@ fn main() { assert_eq!(b + b, false); //~^ bool_assert_comparison } + +fn issue16279() { + macro_rules! is_empty { + ($x:expr) => { + $x.is_empty() + }; + } + + assert_eq!(is_empty!("a"), false); + //~^ bool_assert_comparison + assert_eq!(is_empty!(""), true); + //~^ bool_assert_comparison +} diff --git a/tests/ui/bool_assert_comparison.stderr b/tests/ui/bool_assert_comparison.stderr index 72aa6303a202..b4e8fcf09bb6 100644 --- a/tests/ui/bool_assert_comparison.stderr +++ b/tests/ui/bool_assert_comparison.stderr @@ -444,5 +444,29 @@ LL - assert_eq!(b + b, false); LL + assert!(!(b + b)); | -error: aborting due to 37 previous errors +error: used `assert_eq!` with a literal bool + --> tests/ui/bool_assert_comparison.rs:227:5 + | +LL | assert_eq!(is_empty!("a"), false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(is_empty!("a"), false); +LL + assert!(!is_empty!("a")); + | + +error: used `assert_eq!` with a literal bool + --> tests/ui/bool_assert_comparison.rs:229:5 + | +LL | assert_eq!(is_empty!(""), true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(is_empty!(""), true); +LL + assert!(is_empty!("")); + | + +error: aborting due to 39 previous errors diff --git a/tests/ui/checked_conversions.fixed b/tests/ui/checked_conversions.fixed index 6175275ef047..2309a053146f 100644 --- a/tests/ui/checked_conversions.fixed +++ b/tests/ui/checked_conversions.fixed @@ -1,6 +1,7 @@ #![allow( clippy::cast_lossless, clippy::legacy_numeric_constants, + clippy::no_effect, unused, // Int::max_value will be deprecated in the future deprecated, @@ -105,4 +106,19 @@ fn msrv_1_34() { //~^ checked_conversions } +fn issue16293() { + struct Outer { + inner: u32, + } + let outer = Outer { inner: 42 }; + macro_rules! dot_inner { + ($obj:expr) => { + $obj.inner + }; + } + + i32::try_from(dot_inner!(outer)).is_ok(); + //~^ checked_conversions +} + fn main() {} diff --git a/tests/ui/checked_conversions.rs b/tests/ui/checked_conversions.rs index 9ed0e8f660d0..dabb552eba27 100644 --- a/tests/ui/checked_conversions.rs +++ b/tests/ui/checked_conversions.rs @@ -1,6 +1,7 @@ #![allow( clippy::cast_lossless, clippy::legacy_numeric_constants, + clippy::no_effect, unused, // Int::max_value will be deprecated in the future deprecated, @@ -105,4 +106,19 @@ fn msrv_1_34() { //~^ checked_conversions } +fn issue16293() { + struct Outer { + inner: u32, + } + let outer = Outer { inner: 42 }; + macro_rules! dot_inner { + ($obj:expr) => { + $obj.inner + }; + } + + dot_inner!(outer) <= i32::MAX as u32; + //~^ checked_conversions +} + fn main() {} diff --git a/tests/ui/checked_conversions.stderr b/tests/ui/checked_conversions.stderr index 624876dacb26..6018dacace39 100644 --- a/tests/ui/checked_conversions.stderr +++ b/tests/ui/checked_conversions.stderr @@ -1,5 +1,5 @@ error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:15:13 + --> tests/ui/checked_conversions.rs:16:13 | LL | let _ = value <= (u32::max_value() as i64) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` @@ -8,100 +8,106 @@ LL | let _ = value <= (u32::max_value() as i64) && value >= 0; = help: to override `-D warnings` add `#[allow(clippy::checked_conversions)]` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:17:13 + --> tests/ui/checked_conversions.rs:18:13 | LL | let _ = value <= (u32::MAX as i64) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:22:13 + --> tests/ui/checked_conversions.rs:23:13 | LL | let _ = value <= i64::from(u16::max_value()) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:24:13 + --> tests/ui/checked_conversions.rs:25:13 | LL | let _ = value <= i64::from(u16::MAX) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:29:13 + --> tests/ui/checked_conversions.rs:30:13 | LL | let _ = value <= (u8::max_value() as isize) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:31:13 + --> tests/ui/checked_conversions.rs:32:13 | LL | let _ = value <= (u8::MAX as isize) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:38:13 + --> tests/ui/checked_conversions.rs:39:13 | LL | let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:40:13 + --> tests/ui/checked_conversions.rs:41:13 | LL | let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:45:13 + --> tests/ui/checked_conversions.rs:46:13 | LL | let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:47:13 + --> tests/ui/checked_conversions.rs:48:13 | LL | let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:54:13 + --> tests/ui/checked_conversions.rs:55:13 | LL | let _ = value <= i32::max_value() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:56:13 + --> tests/ui/checked_conversions.rs:57:13 | LL | let _ = value <= i32::MAX as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:61:13 + --> tests/ui/checked_conversions.rs:62:13 | LL | let _ = value <= isize::max_value() as usize && value as i32 == 5; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:63:13 + --> tests/ui/checked_conversions.rs:64:13 | LL | let _ = value <= isize::MAX as usize && value as i32 == 5; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:68:13 + --> tests/ui/checked_conversions.rs:69:13 | LL | let _ = value <= u16::max_value() as u32 && value as i32 == 5; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:70:13 + --> tests/ui/checked_conversions.rs:71:13 | LL | let _ = value <= u16::MAX as u32 && value as i32 == 5; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` error: checked cast can be simplified - --> tests/ui/checked_conversions.rs:104:13 + --> tests/ui/checked_conversions.rs:105:13 | LL | let _ = value <= (u32::max_value() as i64) && value >= 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` -error: aborting due to 17 previous errors +error: checked cast can be simplified + --> tests/ui/checked_conversions.rs:120:5 + | +LL | dot_inner!(outer) <= i32::MAX as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(dot_inner!(outer)).is_ok()` + +error: aborting due to 18 previous errors diff --git a/tests/ui/cmp_owned/with_suggestion.fixed b/tests/ui/cmp_owned/with_suggestion.fixed index 85d0991bef05..4c3b13b30043 100644 --- a/tests/ui/cmp_owned/with_suggestion.fixed +++ b/tests/ui/cmp_owned/with_suggestion.fixed @@ -83,3 +83,32 @@ fn issue_8103() { let _ = foo1 == foo2; //~^ cmp_owned } + +macro_rules! issue16322_macro_generator { + ($locale:ident) => { + mod $locale { + macro_rules! _make { + ($token:tt) => { + stringify!($token) + }; + } + + pub(crate) use _make; + } + + macro_rules! t { + ($token:tt) => { + crate::$locale::_make!($token) + }; + } + }; +} + +issue16322_macro_generator!(de); + +fn issue16322(item: String) { + if item == t!(frohes_neu_Jahr) { + //~^ cmp_owned + println!("Ja!"); + } +} diff --git a/tests/ui/cmp_owned/with_suggestion.rs b/tests/ui/cmp_owned/with_suggestion.rs index 2393757d76f2..a9d7509feaaf 100644 --- a/tests/ui/cmp_owned/with_suggestion.rs +++ b/tests/ui/cmp_owned/with_suggestion.rs @@ -83,3 +83,32 @@ fn issue_8103() { let _ = foo1 == foo2.to_owned(); //~^ cmp_owned } + +macro_rules! issue16322_macro_generator { + ($locale:ident) => { + mod $locale { + macro_rules! _make { + ($token:tt) => { + stringify!($token) + }; + } + + pub(crate) use _make; + } + + macro_rules! t { + ($token:tt) => { + crate::$locale::_make!($token) + }; + } + }; +} + +issue16322_macro_generator!(de); + +fn issue16322(item: String) { + if item == t!(frohes_neu_Jahr).to_string() { + //~^ cmp_owned + println!("Ja!"); + } +} diff --git a/tests/ui/cmp_owned/with_suggestion.stderr b/tests/ui/cmp_owned/with_suggestion.stderr index dd9ffa70897a..66544ce0c217 100644 --- a/tests/ui/cmp_owned/with_suggestion.stderr +++ b/tests/ui/cmp_owned/with_suggestion.stderr @@ -49,5 +49,11 @@ error: this creates an owned instance just for comparison LL | let _ = foo1 == foo2.to_owned(); | ^^^^^^^^^^^^^^^ help: try: `foo2` -error: aborting due to 8 previous errors +error: this creates an owned instance just for comparison + --> tests/ui/cmp_owned/with_suggestion.rs:110:16 + | +LL | if item == t!(frohes_neu_Jahr).to_string() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t!(frohes_neu_Jahr)` + +error: aborting due to 9 previous errors diff --git a/tests/ui/derive_ord_xor_partial_ord.rs b/tests/ui/derive_ord_xor_partial_ord.rs index b4bb24b0d2fe..386ab39401c5 100644 --- a/tests/ui/derive_ord_xor_partial_ord.rs +++ b/tests/ui/derive_ord_xor_partial_ord.rs @@ -91,3 +91,17 @@ mod issue15708 { } } } + +mod issue16298 { + #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] + struct Normalized(S); + + impl Eq for Normalized {} + + #[expect(clippy::derive_ord_xor_partial_ord)] + impl Ord for Normalized { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap() + } + } +} diff --git a/tests/ui/double_parens.fixed b/tests/ui/double_parens.fixed index 024af6840132..ef7838491f8f 100644 --- a/tests/ui/double_parens.fixed +++ b/tests/ui/double_parens.fixed @@ -161,4 +161,20 @@ fn issue15940() { pub struct Person; } +fn issue16224() { + fn test() -> i32 { 42 } + + macro_rules! call { + ($matcher:pat $(=> $result:expr)?) => { + match test() { + $matcher => Result::Ok(($($result),*)), + _ => Result::Err("No match".to_string()), + } + }; + } + + let _: Result<(), String> = call!(_); + let _: Result = call!(_ => 42); +} + fn main() {} diff --git a/tests/ui/double_parens.rs b/tests/ui/double_parens.rs index 8a76f2837f35..07eafdf69575 100644 --- a/tests/ui/double_parens.rs +++ b/tests/ui/double_parens.rs @@ -161,4 +161,20 @@ fn issue15940() { pub struct Person; } +fn issue16224() { + fn test() -> i32 { 42 } + + macro_rules! call { + ($matcher:pat $(=> $result:expr)?) => { + match test() { + $matcher => Result::Ok(($($result),*)), + _ => Result::Err("No match".to_string()), + } + }; + } + + let _: Result<(), String> = call!(_); + let _: Result = call!(_ => 42); +} + fn main() {} diff --git a/tests/ui/for_kv_map.fixed b/tests/ui/for_kv_map.fixed index 2a68b7443fbf..6ec4cb01ffd1 100644 --- a/tests/ui/for_kv_map.fixed +++ b/tests/ui/for_kv_map.fixed @@ -69,3 +69,20 @@ fn main() { let _v = v; } } + +fn wrongly_unmangled_macros() { + use std::collections::HashMap; + + macro_rules! test_map { + ($val:expr) => { + &*$val + }; + } + + let m: HashMap = HashMap::new(); + let wrapped = Rc::new(m); + for v in test_map!(wrapped).values() { + //~^ for_kv_map + let _v = v; + } +} diff --git a/tests/ui/for_kv_map.rs b/tests/ui/for_kv_map.rs index 485a97815e3c..19e907ff10a6 100644 --- a/tests/ui/for_kv_map.rs +++ b/tests/ui/for_kv_map.rs @@ -69,3 +69,20 @@ fn main() { let _v = v; } } + +fn wrongly_unmangled_macros() { + use std::collections::HashMap; + + macro_rules! test_map { + ($val:expr) => { + &*$val + }; + } + + let m: HashMap = HashMap::new(); + let wrapped = Rc::new(m); + for (_, v) in test_map!(wrapped) { + //~^ for_kv_map + let _v = v; + } +} diff --git a/tests/ui/for_kv_map.stderr b/tests/ui/for_kv_map.stderr index 0bd474a10682..5436592f2ab6 100644 --- a/tests/ui/for_kv_map.stderr +++ b/tests/ui/for_kv_map.stderr @@ -72,5 +72,17 @@ LL - 'label: for (k, _value) in rm { LL + 'label: for k in rm.keys() { | -error: aborting due to 6 previous errors +error: you seem to want to iterate on a map's values + --> tests/ui/for_kv_map.rs:84:19 + | +LL | for (_, v) in test_map!(wrapped) { + | ^^^^^^^^^^^^^^^^^^ + | +help: use the corresponding method + | +LL - for (_, v) in test_map!(wrapped) { +LL + for v in test_map!(wrapped).values() { + | + +error: aborting due to 7 previous errors diff --git a/tests/ui/impl.rs b/tests/ui/impl.rs index e6044cc50781..75761a34c86e 100644 --- a/tests/ui/impl.rs +++ b/tests/ui/impl.rs @@ -14,6 +14,7 @@ impl MyStruct { } impl<'a> MyStruct { + //~^ multiple_inherent_impl fn lifetimed() {} } @@ -90,10 +91,12 @@ struct Lifetime<'s> { } impl Lifetime<'_> {} -impl Lifetime<'_> {} // false negative +impl Lifetime<'_> {} +//~^ multiple_inherent_impl impl<'a> Lifetime<'a> {} -impl<'a> Lifetime<'a> {} // false negative +impl<'a> Lifetime<'a> {} +//~^ multiple_inherent_impl impl<'b> Lifetime<'b> {} // false negative? @@ -104,6 +107,39 @@ struct Generic { } impl Generic {} -impl Generic {} // false negative +impl Generic {} +//~^ multiple_inherent_impl + +use std::fmt::Debug; + +#[derive(Debug)] +struct GenericWithBounds(T); + +impl GenericWithBounds { + fn make_one(_one: T) -> Self { + todo!() + } +} + +impl GenericWithBounds { + //~^ multiple_inherent_impl + fn make_two(_two: T) -> Self { + todo!() + } +} + +struct MultipleTraitBounds(T); + +impl MultipleTraitBounds { + fn debug_fn() {} +} + +impl MultipleTraitBounds { + fn clone_fn() {} +} + +impl MultipleTraitBounds { + fn debug_clone_fn() {} +} fn main() {} diff --git a/tests/ui/impl.stderr b/tests/ui/impl.stderr index 93d4b3998f90..9c4aaf183d70 100644 --- a/tests/ui/impl.stderr +++ b/tests/ui/impl.stderr @@ -19,7 +19,24 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::multiple_inherent_impl)]` error: multiple implementations of this structure - --> tests/ui/impl.rs:26:5 + --> tests/ui/impl.rs:16:1 + | +LL | / impl<'a> MyStruct { +LL | | +LL | | fn lifetimed() {} +LL | | } + | |_^ + | +note: first implementation here + --> tests/ui/impl.rs:6:1 + | +LL | / impl MyStruct { +LL | | fn first() {} +LL | | } + | |_^ + +error: multiple implementations of this structure + --> tests/ui/impl.rs:27:5 | LL | / impl super::MyStruct { LL | | @@ -37,7 +54,7 @@ LL | | } | |_^ error: multiple implementations of this structure - --> tests/ui/impl.rs:48:1 + --> tests/ui/impl.rs:49:1 | LL | / impl WithArgs { LL | | @@ -47,7 +64,7 @@ LL | | } | |_^ | note: first implementation here - --> tests/ui/impl.rs:45:1 + --> tests/ui/impl.rs:46:1 | LL | / impl WithArgs { LL | | fn f2() {} @@ -55,28 +72,85 @@ LL | | } | |_^ error: multiple implementations of this structure - --> tests/ui/impl.rs:71:1 + --> tests/ui/impl.rs:72:1 | LL | impl OneAllowedImpl {} | ^^^^^^^^^^^^^^^^^^^^^^ | note: first implementation here - --> tests/ui/impl.rs:68:1 + --> tests/ui/impl.rs:69:1 | LL | impl OneAllowedImpl {} | ^^^^^^^^^^^^^^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/impl.rs:84:1 + --> tests/ui/impl.rs:85:1 | LL | impl OneExpected {} | ^^^^^^^^^^^^^^^^^^^ | note: first implementation here - --> tests/ui/impl.rs:81:1 + --> tests/ui/impl.rs:82:1 | LL | impl OneExpected {} | ^^^^^^^^^^^^^^^^^^^ -error: aborting due to 5 previous errors +error: multiple implementations of this structure + --> tests/ui/impl.rs:94:1 + | +LL | impl Lifetime<'_> {} + | ^^^^^^^^^^^^^^^^^^^^ + | +note: first implementation here + --> tests/ui/impl.rs:93:1 + | +LL | impl Lifetime<'_> {} + | ^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/impl.rs:98:1 + | +LL | impl<'a> Lifetime<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: first implementation here + --> tests/ui/impl.rs:97:1 + | +LL | impl<'a> Lifetime<'a> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/impl.rs:110:1 + | +LL | impl Generic {} + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: first implementation here + --> tests/ui/impl.rs:109:1 + | +LL | impl Generic {} + | ^^^^^^^^^^^^^^^^^^^^^ + +error: multiple implementations of this structure + --> tests/ui/impl.rs:124:1 + | +LL | / impl GenericWithBounds { +LL | | +LL | | fn make_two(_two: T) -> Self { +LL | | todo!() +LL | | } +LL | | } + | |_^ + | +note: first implementation here + --> tests/ui/impl.rs:118:1 + | +LL | / impl GenericWithBounds { +LL | | fn make_one(_one: T) -> Self { +LL | | todo!() +LL | | } +LL | | } + | |_^ + +error: aborting due to 10 previous errors diff --git a/tests/ui/implicit_saturating_sub.fixed b/tests/ui/implicit_saturating_sub.fixed index 1aab6c54407e..22e59bbd2705 100644 --- a/tests/ui/implicit_saturating_sub.fixed +++ b/tests/ui/implicit_saturating_sub.fixed @@ -252,3 +252,11 @@ fn arbitrary_expression() { 0 }; } + +fn issue16307() { + let x: u8 = 100; + let y = 100_u8.saturating_sub(x); + //~^ implicit_saturating_sub + + println!("{y}"); +} diff --git a/tests/ui/implicit_saturating_sub.rs b/tests/ui/implicit_saturating_sub.rs index 7ca57a2902db..7fa19f0c8ad2 100644 --- a/tests/ui/implicit_saturating_sub.rs +++ b/tests/ui/implicit_saturating_sub.rs @@ -326,3 +326,11 @@ fn arbitrary_expression() { 0 }; } + +fn issue16307() { + let x: u8 = 100; + let y = if x >= 100 { 0 } else { 100 - x }; + //~^ implicit_saturating_sub + + println!("{y}"); +} diff --git a/tests/ui/implicit_saturating_sub.stderr b/tests/ui/implicit_saturating_sub.stderr index 0c225856fd07..2f3d2ba787e8 100644 --- a/tests/ui/implicit_saturating_sub.stderr +++ b/tests/ui/implicit_saturating_sub.stderr @@ -238,5 +238,11 @@ error: manual arithmetic check found LL | let _ = if a < b * 2 { 0 } else { a - b * 2 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a.saturating_sub(b * 2)` -error: aborting due to 27 previous errors +error: manual arithmetic check found + --> tests/ui/implicit_saturating_sub.rs:332:13 + | +LL | let y = if x >= 100 { 0 } else { 100 - x }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `100_u8.saturating_sub(x)` + +error: aborting due to 28 previous errors diff --git a/tests/ui/iter_kv_map.fixed b/tests/ui/iter_kv_map.fixed index b18dda358877..189d76bc9431 100644 --- a/tests/ui/iter_kv_map.fixed +++ b/tests/ui/iter_kv_map.fixed @@ -189,3 +189,9 @@ fn issue14595() { let _ = map.as_ref().values().copied().collect::>(); //~^ iter_kv_map } + +fn issue16340() { + let hm: HashMap<&str, &str> = HashMap::new(); + let _ = hm.keys().map(|key| vec![key]); + //~^ iter_kv_map +} diff --git a/tests/ui/iter_kv_map.rs b/tests/ui/iter_kv_map.rs index 729e4e8a266c..cfc303447004 100644 --- a/tests/ui/iter_kv_map.rs +++ b/tests/ui/iter_kv_map.rs @@ -193,3 +193,9 @@ fn issue14595() { let _ = map.as_ref().iter().map(|(_, v)| v).copied().collect::>(); //~^ iter_kv_map } + +fn issue16340() { + let hm: HashMap<&str, &str> = HashMap::new(); + let _ = hm.iter().map(|(key, _)| vec![key]); + //~^ iter_kv_map +} diff --git a/tests/ui/iter_kv_map.stderr b/tests/ui/iter_kv_map.stderr index 8f73541f5033..866e69ea1922 100644 --- a/tests/ui/iter_kv_map.stderr +++ b/tests/ui/iter_kv_map.stderr @@ -269,5 +269,11 @@ error: iterating on a map's values LL | let _ = map.as_ref().iter().map(|(_, v)| v).copied().collect::>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.as_ref().values()` -error: aborting due to 39 previous errors +error: iterating on a map's keys + --> tests/ui/iter_kv_map.rs:199:13 + | +LL | let _ = hm.iter().map(|(key, _)| vec![key]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hm.keys().map(|key| vec![key])` + +error: aborting due to 40 previous errors diff --git a/tests/ui/manual_div_ceil.fixed b/tests/ui/manual_div_ceil.fixed index cd91be87ec17..8ffd107dd42e 100644 --- a/tests/ui/manual_div_ceil.fixed +++ b/tests/ui/manual_div_ceil.fixed @@ -105,3 +105,32 @@ fn issue_15705(size: u64, c: &u64) { let _ = size.div_ceil(*c); //~^ manual_div_ceil } + +struct MyStruct(u32); +impl MyStruct { + // Method matching name on different type should not trigger lint + fn next_multiple_of(self, y: u32) -> u32 { + self.0.next_multiple_of(y) + } +} + +fn issue_16219() { + let x = 33u32; + + // Lint. + let _ = x.div_ceil(8); + //~^ manual_div_ceil + let _ = x.div_ceil(8); + //~^ manual_div_ceil + + let y = &x; + let _ = y.div_ceil(8); + //~^ manual_div_ceil + + // No lint. + let _ = x.next_multiple_of(8) / 7; + let _ = x.next_multiple_of(7) / 8; + + let z = MyStruct(x); + let _ = z.next_multiple_of(8) / 8; +} diff --git a/tests/ui/manual_div_ceil.rs b/tests/ui/manual_div_ceil.rs index 9899c7d775c2..859fb5a13c44 100644 --- a/tests/ui/manual_div_ceil.rs +++ b/tests/ui/manual_div_ceil.rs @@ -105,3 +105,32 @@ fn issue_15705(size: u64, c: &u64) { let _ = (size + c - 1) / c; //~^ manual_div_ceil } + +struct MyStruct(u32); +impl MyStruct { + // Method matching name on different type should not trigger lint + fn next_multiple_of(self, y: u32) -> u32 { + self.0.next_multiple_of(y) + } +} + +fn issue_16219() { + let x = 33u32; + + // Lint. + let _ = x.next_multiple_of(8) / 8; + //~^ manual_div_ceil + let _ = u32::next_multiple_of(x, 8) / 8; + //~^ manual_div_ceil + + let y = &x; + let _ = y.next_multiple_of(8) / 8; + //~^ manual_div_ceil + + // No lint. + let _ = x.next_multiple_of(8) / 7; + let _ = x.next_multiple_of(7) / 8; + + let z = MyStruct(x); + let _ = z.next_multiple_of(8) / 8; +} diff --git a/tests/ui/manual_div_ceil.stderr b/tests/ui/manual_div_ceil.stderr index 44de3ba99be7..0efc114c7078 100644 --- a/tests/ui/manual_div_ceil.stderr +++ b/tests/ui/manual_div_ceil.stderr @@ -131,5 +131,23 @@ error: manually reimplementing `div_ceil` LL | let _ = (size + c - 1) / c; | ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `size.div_ceil(*c)` -error: aborting due to 20 previous errors +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:121:13 + | +LL | let _ = x.next_multiple_of(8) / 8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:123:13 + | +LL | let _ = u32::next_multiple_of(x, 8) / 8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:127:13 + | +LL | let _ = y.next_multiple_of(8) / 8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(8)` + +error: aborting due to 23 previous errors diff --git a/tests/ui/manual_div_ceil_with_feature.fixed b/tests/ui/manual_div_ceil_with_feature.fixed index f55999c5ad08..d77b8bc28986 100644 --- a/tests/ui/manual_div_ceil_with_feature.fixed +++ b/tests/ui/manual_div_ceil_with_feature.fixed @@ -84,3 +84,32 @@ fn issue_13950() { let _ = y.div_ceil(-7); //~^ manual_div_ceil } + +struct MyStruct(i32); +impl MyStruct { + // Method matching name on different type should not trigger lint + fn next_multiple_of(self, y: i32) -> i32 { + self.0.next_multiple_of(y) + } +} + +fn issue_16219() { + let x = 33i32; + + // Lint. + let _ = x.div_ceil(8); + //~^ manual_div_ceil + let _ = x.div_ceil(8); + //~^ manual_div_ceil + + let y = &x; + let _ = y.div_ceil(8); + //~^ manual_div_ceil + + // No lint. + let _ = x.next_multiple_of(8) / 7; + let _ = x.next_multiple_of(7) / 8; + + let z = MyStruct(x); + let _ = z.next_multiple_of(8) / 8; +} diff --git a/tests/ui/manual_div_ceil_with_feature.rs b/tests/ui/manual_div_ceil_with_feature.rs index 8a895f634cb4..5b9a4d9156a7 100644 --- a/tests/ui/manual_div_ceil_with_feature.rs +++ b/tests/ui/manual_div_ceil_with_feature.rs @@ -84,3 +84,32 @@ fn issue_13950() { let _ = (y - 8) / -7; //~^ manual_div_ceil } + +struct MyStruct(i32); +impl MyStruct { + // Method matching name on different type should not trigger lint + fn next_multiple_of(self, y: i32) -> i32 { + self.0.next_multiple_of(y) + } +} + +fn issue_16219() { + let x = 33i32; + + // Lint. + let _ = x.next_multiple_of(8) / 8; + //~^ manual_div_ceil + let _ = i32::next_multiple_of(x, 8) / 8; + //~^ manual_div_ceil + + let y = &x; + let _ = y.next_multiple_of(8) / 8; + //~^ manual_div_ceil + + // No lint. + let _ = x.next_multiple_of(8) / 7; + let _ = x.next_multiple_of(7) / 8; + + let z = MyStruct(x); + let _ = z.next_multiple_of(8) / 8; +} diff --git a/tests/ui/manual_div_ceil_with_feature.stderr b/tests/ui/manual_div_ceil_with_feature.stderr index e1160d962996..c5fa15112a87 100644 --- a/tests/ui/manual_div_ceil_with_feature.stderr +++ b/tests/ui/manual_div_ceil_with_feature.stderr @@ -139,5 +139,23 @@ error: manually reimplementing `div_ceil` LL | let _ = (y - 8) / -7; | ^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(-7)` -error: aborting due to 23 previous errors +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:100:13 + | +LL | let _ = x.next_multiple_of(8) / 8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:102:13 + | +LL | let _ = i32::next_multiple_of(x, 8) / 8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:106:13 + | +LL | let _ = y.next_multiple_of(8) / 8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(8)` + +error: aborting due to 26 previous errors diff --git a/tests/ui/manual_ignore_case_cmp.fixed b/tests/ui/manual_ignore_case_cmp.fixed index cd7adc20b127..f0e413aaec0d 100644 --- a/tests/ui/manual_ignore_case_cmp.fixed +++ b/tests/ui/manual_ignore_case_cmp.fixed @@ -160,3 +160,23 @@ fn ref_osstring(a: OsString, b: &OsString) { b.eq_ignore_ascii_case(a); //~^ manual_ignore_case_cmp } + +fn wrongly_unmangled_macros(a: &str, b: &str) -> bool { + struct S<'a> { + inner: &'a str, + } + + let a = S { inner: a }; + let b = S { inner: b }; + + macro_rules! dot_inner { + ($s:expr) => { + $s.inner + }; + } + + dot_inner!(a).eq_ignore_ascii_case(dot_inner!(b)) + //~^ manual_ignore_case_cmp + || dot_inner!(a).eq_ignore_ascii_case("abc") + //~^ manual_ignore_case_cmp +} diff --git a/tests/ui/manual_ignore_case_cmp.rs b/tests/ui/manual_ignore_case_cmp.rs index 85f6719827c9..9802e87cd233 100644 --- a/tests/ui/manual_ignore_case_cmp.rs +++ b/tests/ui/manual_ignore_case_cmp.rs @@ -160,3 +160,23 @@ fn ref_osstring(a: OsString, b: &OsString) { b.to_ascii_lowercase() == a.to_ascii_lowercase(); //~^ manual_ignore_case_cmp } + +fn wrongly_unmangled_macros(a: &str, b: &str) -> bool { + struct S<'a> { + inner: &'a str, + } + + let a = S { inner: a }; + let b = S { inner: b }; + + macro_rules! dot_inner { + ($s:expr) => { + $s.inner + }; + } + + dot_inner!(a).to_ascii_lowercase() == dot_inner!(b).to_ascii_lowercase() + //~^ manual_ignore_case_cmp + || dot_inner!(a).to_ascii_lowercase() == "abc" + //~^ manual_ignore_case_cmp +} diff --git a/tests/ui/manual_ignore_case_cmp.stderr b/tests/ui/manual_ignore_case_cmp.stderr index fa7fadd91076..2f698e076ed3 100644 --- a/tests/ui/manual_ignore_case_cmp.stderr +++ b/tests/ui/manual_ignore_case_cmp.stderr @@ -588,5 +588,29 @@ LL - b.to_ascii_lowercase() == a.to_ascii_lowercase(); LL + b.eq_ignore_ascii_case(a); | -error: aborting due to 49 previous errors +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:178:5 + | +LL | dot_inner!(a).to_ascii_lowercase() == dot_inner!(b).to_ascii_lowercase() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL - dot_inner!(a).to_ascii_lowercase() == dot_inner!(b).to_ascii_lowercase() +LL + dot_inner!(a).eq_ignore_ascii_case(dot_inner!(b)) + | + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:180:12 + | +LL | || dot_inner!(a).to_ascii_lowercase() == "abc" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL - || dot_inner!(a).to_ascii_lowercase() == "abc" +LL + || dot_inner!(a).eq_ignore_ascii_case("abc") + | + +error: aborting due to 51 previous errors diff --git a/tests/ui/manual_ilog2.fixed b/tests/ui/manual_ilog2.fixed index a0f6d9392c30..ea86fc927c7c 100644 --- a/tests/ui/manual_ilog2.fixed +++ b/tests/ui/manual_ilog2.fixed @@ -30,3 +30,20 @@ fn foo(a: u32, b: u64) { external!($a.ilog(2)); with_span!(span; a.ilog(2)); } + +fn wrongly_unmangled_macros() { + struct S { + inner: u32, + } + + let x = S { inner: 42 }; + macro_rules! access { + ($s:expr) => { + $s.inner + }; + } + let log = access!(x).ilog2(); + //~^ manual_ilog2 + let log = access!(x).ilog2(); + //~^ manual_ilog2 +} diff --git a/tests/ui/manual_ilog2.rs b/tests/ui/manual_ilog2.rs index bd4b5d9d3c0d..8cb0e12d7361 100644 --- a/tests/ui/manual_ilog2.rs +++ b/tests/ui/manual_ilog2.rs @@ -30,3 +30,20 @@ fn foo(a: u32, b: u64) { external!($a.ilog(2)); with_span!(span; a.ilog(2)); } + +fn wrongly_unmangled_macros() { + struct S { + inner: u32, + } + + let x = S { inner: 42 }; + macro_rules! access { + ($s:expr) => { + $s.inner + }; + } + let log = 31 - access!(x).leading_zeros(); + //~^ manual_ilog2 + let log = access!(x).ilog(2); + //~^ manual_ilog2 +} diff --git a/tests/ui/manual_ilog2.stderr b/tests/ui/manual_ilog2.stderr index 7c9694f35330..d0ef8378081a 100644 --- a/tests/ui/manual_ilog2.stderr +++ b/tests/ui/manual_ilog2.stderr @@ -19,5 +19,17 @@ error: manually reimplementing `ilog2` LL | 63 - b.leading_zeros(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `b.ilog2()` -error: aborting due to 3 previous errors +error: manually reimplementing `ilog2` + --> tests/ui/manual_ilog2.rs:45:15 + | +LL | let log = 31 - access!(x).leading_zeros(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `access!(x).ilog2()` + +error: manually reimplementing `ilog2` + --> tests/ui/manual_ilog2.rs:47:15 + | +LL | let log = access!(x).ilog(2); + | ^^^^^^^^^^^^^^^^^^ help: try: `access!(x).ilog2()` + +error: aborting due to 5 previous errors diff --git a/tests/ui/manual_is_multiple_of.fixed b/tests/ui/manual_is_multiple_of.fixed index 03f75e725ed5..82e0684e5e57 100644 --- a/tests/ui/manual_is_multiple_of.fixed +++ b/tests/ui/manual_is_multiple_of.fixed @@ -101,3 +101,19 @@ mod issue15103 { (1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum() } } + +fn wrongly_unmangled_macros(a: u32, b: u32) { + struct Wrapper(u32); + let a = Wrapper(a); + let b = Wrapper(b); + macro_rules! dot_0 { + ($x:expr) => { + $x.0 + }; + } + + if dot_0!(a).is_multiple_of(dot_0!(b)) { + //~^ manual_is_multiple_of + todo!() + } +} diff --git a/tests/ui/manual_is_multiple_of.rs b/tests/ui/manual_is_multiple_of.rs index 7b6fa64c843d..82a492e24092 100644 --- a/tests/ui/manual_is_multiple_of.rs +++ b/tests/ui/manual_is_multiple_of.rs @@ -101,3 +101,19 @@ mod issue15103 { (1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum() } } + +fn wrongly_unmangled_macros(a: u32, b: u32) { + struct Wrapper(u32); + let a = Wrapper(a); + let b = Wrapper(b); + macro_rules! dot_0 { + ($x:expr) => { + $x.0 + }; + } + + if dot_0!(a) % dot_0!(b) == 0 { + //~^ manual_is_multiple_of + todo!() + } +} diff --git a/tests/ui/manual_is_multiple_of.stderr b/tests/ui/manual_is_multiple_of.stderr index 8523599ec402..3aba869c9111 100644 --- a/tests/ui/manual_is_multiple_of.stderr +++ b/tests/ui/manual_is_multiple_of.stderr @@ -67,5 +67,11 @@ error: manual implementation of `.is_multiple_of()` LL | let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() }; | ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*i)` -error: aborting due to 11 previous errors +error: manual implementation of `.is_multiple_of()` + --> tests/ui/manual_is_multiple_of.rs:115:8 + | +LL | if dot_0!(a) % dot_0!(b) == 0 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `dot_0!(a).is_multiple_of(dot_0!(b))` + +error: aborting due to 12 previous errors diff --git a/tests/ui/manual_ok_err.fixed b/tests/ui/manual_ok_err.fixed index 9b70ce0df43a..e22f91a0155f 100644 --- a/tests/ui/manual_ok_err.fixed +++ b/tests/ui/manual_ok_err.fixed @@ -127,3 +127,13 @@ mod issue15051 { result_with_ref_mut(x).as_mut().ok() } } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + Ok::($val) + }; + } + + let _ = test_expr!(42).ok(); +} diff --git a/tests/ui/manual_ok_err.rs b/tests/ui/manual_ok_err.rs index dee904638245..c1355f0d4096 100644 --- a/tests/ui/manual_ok_err.rs +++ b/tests/ui/manual_ok_err.rs @@ -177,3 +177,17 @@ mod issue15051 { } } } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + Ok::($val) + }; + } + + let _ = match test_expr!(42) { + //~^ manual_ok_err + Ok(v) => Some(v), + Err(_) => None, + }; +} diff --git a/tests/ui/manual_ok_err.stderr b/tests/ui/manual_ok_err.stderr index 448fbffc0509..0c2ed368eb97 100644 --- a/tests/ui/manual_ok_err.stderr +++ b/tests/ui/manual_ok_err.stderr @@ -141,5 +141,16 @@ LL | | Err(_) => None, LL | | } | |_________^ help: replace with: `result_with_ref_mut(x).as_mut().ok()` -error: aborting due to 12 previous errors +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:188:13 + | +LL | let _ = match test_expr!(42) { + | _____________^ +LL | | +LL | | Ok(v) => Some(v), +LL | | Err(_) => None, +LL | | }; + | |_____^ help: replace with: `test_expr!(42).ok()` + +error: aborting due to 13 previous errors diff --git a/tests/ui/match_as_ref.fixed b/tests/ui/match_as_ref.fixed index 09a6ed169390..b1b8ffb885f5 100644 --- a/tests/ui/match_as_ref.fixed +++ b/tests/ui/match_as_ref.fixed @@ -90,3 +90,13 @@ fn issue15932() { let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _); } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + Some($val) + }; + } + + let _: Option<&u32> = test_expr!(42).as_ref(); +} diff --git a/tests/ui/match_as_ref.rs b/tests/ui/match_as_ref.rs index 347b6d186887..3113167957d4 100644 --- a/tests/ui/match_as_ref.rs +++ b/tests/ui/match_as_ref.rs @@ -114,3 +114,17 @@ fn issue15932() { Some(ref mut v) => Some(v), }; } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + Some($val) + }; + } + + let _: Option<&u32> = match test_expr!(42) { + //~^ match_as_ref + None => None, + Some(ref v) => Some(v), + }; +} diff --git a/tests/ui/match_as_ref.stderr b/tests/ui/match_as_ref.stderr index df06e358f296..3eab499fe409 100644 --- a/tests/ui/match_as_ref.stderr +++ b/tests/ui/match_as_ref.stderr @@ -127,5 +127,26 @@ LL - }; LL + let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _); | -error: aborting due to 6 previous errors +error: manual implementation of `Option::as_ref` + --> tests/ui/match_as_ref.rs:125:27 + | +LL | let _: Option<&u32> = match test_expr!(42) { + | ___________________________^ +LL | | +LL | | None => None, +LL | | Some(ref v) => Some(v), +LL | | }; + | |_____^ + | +help: use `Option::as_ref()` directly + | +LL - let _: Option<&u32> = match test_expr!(42) { +LL - +LL - None => None, +LL - Some(ref v) => Some(v), +LL - }; +LL + let _: Option<&u32> = test_expr!(42).as_ref(); + | + +error: aborting due to 7 previous errors diff --git a/tests/ui/match_bool.fixed b/tests/ui/match_bool.fixed index 876ae935afde..3d5d0a0d532c 100644 --- a/tests/ui/match_bool.fixed +++ b/tests/ui/match_bool.fixed @@ -74,4 +74,15 @@ fn issue15351() { } } +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + ($val + 1) > 0 + }; + } + + let x = 5; + if test_expr!(x) { 1 } else { 0 }; +} + fn main() {} diff --git a/tests/ui/match_bool.rs b/tests/ui/match_bool.rs index a134ad8346e2..4db0aedf3260 100644 --- a/tests/ui/match_bool.rs +++ b/tests/ui/match_bool.rs @@ -126,4 +126,19 @@ fn issue15351() { } } +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + ($val + 1) > 0 + }; + } + + let x = 5; + match test_expr!(x) { + //~^ match_bool + true => 1, + false => 0, + }; +} + fn main() {} diff --git a/tests/ui/match_bool.stderr b/tests/ui/match_bool.stderr index c05742e56339..223acd17aead 100644 --- a/tests/ui/match_bool.stderr +++ b/tests/ui/match_bool.stderr @@ -183,5 +183,15 @@ LL + break 'a; LL + } } | -error: aborting due to 13 previous errors +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:137:5 + | +LL | / match test_expr!(x) { +LL | | +LL | | true => 1, +LL | | false => 0, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if test_expr!(x) { 1 } else { 0 }` + +error: aborting due to 14 previous errors diff --git a/tests/ui/mutex_atomic.fixed b/tests/ui/mutex_atomic.fixed index e4218726019f..dc05d8a2c61f 100644 --- a/tests/ui/mutex_atomic.fixed +++ b/tests/ui/mutex_atomic.fixed @@ -65,3 +65,15 @@ fn issue13378() { let (funky_mtx) = std::sync::atomic::AtomicU64::new(0); //~^ mutex_integer } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + ($val > 0 && true) + }; + } + + let _ = std::sync::atomic::AtomicBool::new(test_expr!(1)); + //~^ mutex_atomic + // The suggestion should preserve the macro call: `AtomicBool::new(test_expr!(true))` +} diff --git a/tests/ui/mutex_atomic.rs b/tests/ui/mutex_atomic.rs index 95f2b135903f..33745f8fc5e1 100644 --- a/tests/ui/mutex_atomic.rs +++ b/tests/ui/mutex_atomic.rs @@ -65,3 +65,15 @@ fn issue13378() { let (funky_mtx): Mutex = Mutex::new(0); //~^ mutex_integer } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + ($val > 0 && true) + }; + } + + let _ = Mutex::new(test_expr!(1)); + //~^ mutex_atomic + // The suggestion should preserve the macro call: `AtomicBool::new(test_expr!(true))` +} diff --git a/tests/ui/mutex_atomic.stderr b/tests/ui/mutex_atomic.stderr index 0afc6d541dea..56d94035583c 100644 --- a/tests/ui/mutex_atomic.stderr +++ b/tests/ui/mutex_atomic.stderr @@ -130,5 +130,13 @@ LL - let (funky_mtx): Mutex = Mutex::new(0); LL + let (funky_mtx) = std::sync::atomic::AtomicU64::new(0); | -error: aborting due to 14 previous errors +error: using a `Mutex` where an atomic would do + --> tests/ui/mutex_atomic.rs:76:13 + | +LL | let _ = Mutex::new(test_expr!(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::sync::atomic::AtomicBool::new(test_expr!(1))` + | + = help: if you just want the locking behavior and not the internal type, consider using `Mutex<()>` + +error: aborting due to 15 previous errors diff --git a/tests/ui/needless_bool_assign.fixed b/tests/ui/needless_bool_assign.fixed index d6fab4c51b53..8fd572038140 100644 --- a/tests/ui/needless_bool_assign.fixed +++ b/tests/ui/needless_bool_assign.fixed @@ -42,3 +42,22 @@ fn issue15063(x: bool, y: bool) { } else { z = x || y; } //~^^^^^ needless_bool_assign } + +fn wrongly_unmangled_macros(must_keep: fn(usize, usize) -> bool, x: usize, y: usize) { + struct Wrapper(T); + let mut skip = Wrapper(false); + + macro_rules! invoke { + ($func:expr, $a:expr, $b:expr) => { + $func($a, $b) + }; + } + macro_rules! dot_0 { + ($w:expr) => { + $w.0 + }; + } + + dot_0!(skip) = !invoke!(must_keep, x, y); + //~^^^^^ needless_bool_assign +} diff --git a/tests/ui/needless_bool_assign.rs b/tests/ui/needless_bool_assign.rs index c504f61f4dd1..4721ab433b32 100644 --- a/tests/ui/needless_bool_assign.rs +++ b/tests/ui/needless_bool_assign.rs @@ -58,3 +58,26 @@ fn issue15063(x: bool, y: bool) { } //~^^^^^ needless_bool_assign } + +fn wrongly_unmangled_macros(must_keep: fn(usize, usize) -> bool, x: usize, y: usize) { + struct Wrapper(T); + let mut skip = Wrapper(false); + + macro_rules! invoke { + ($func:expr, $a:expr, $b:expr) => { + $func($a, $b) + }; + } + macro_rules! dot_0 { + ($w:expr) => { + $w.0 + }; + } + + if invoke!(must_keep, x, y) { + dot_0!(skip) = false; + } else { + dot_0!(skip) = true; + } + //~^^^^^ needless_bool_assign +} diff --git a/tests/ui/needless_bool_assign.stderr b/tests/ui/needless_bool_assign.stderr index 1d09b8b25a09..34ff782f34a3 100644 --- a/tests/ui/needless_bool_assign.stderr +++ b/tests/ui/needless_bool_assign.stderr @@ -62,5 +62,15 @@ LL | | z = false; LL | | } | |_____^ help: you can reduce it to: `{ z = x || y; }` -error: aborting due to 5 previous errors +error: this if-then-else expression assigns a bool literal + --> tests/ui/needless_bool_assign.rs:77:5 + | +LL | / if invoke!(must_keep, x, y) { +LL | | dot_0!(skip) = false; +LL | | } else { +LL | | dot_0!(skip) = true; +LL | | } + | |_____^ help: you can reduce it to: `dot_0!(skip) = !invoke!(must_keep, x, y);` + +error: aborting due to 6 previous errors diff --git a/tests/ui/needless_for_each_fixable.fixed b/tests/ui/needless_for_each_fixable.fixed index a6d64d9afc1a..19b34f42af24 100644 --- a/tests/ui/needless_for_each_fixable.fixed +++ b/tests/ui/needless_for_each_fixable.fixed @@ -149,3 +149,11 @@ fn issue15256() { for v in vec.iter() { println!("{v}"); } //~^ needless_for_each } + +fn issue16294() { + let vec: Vec = Vec::new(); + for elem in vec.iter() { + //~^ needless_for_each + println!("{elem}"); + } +} diff --git a/tests/ui/needless_for_each_fixable.rs b/tests/ui/needless_for_each_fixable.rs index 7e74d2b428fd..f04e2555a370 100644 --- a/tests/ui/needless_for_each_fixable.rs +++ b/tests/ui/needless_for_each_fixable.rs @@ -149,3 +149,11 @@ fn issue15256() { vec.iter().for_each(|v| println!("{v}")); //~^ needless_for_each } + +fn issue16294() { + let vec: Vec = Vec::new(); + vec.iter().for_each(|elem| { + //~^ needless_for_each + println!("{elem}"); + }) +} diff --git a/tests/ui/needless_for_each_fixable.stderr b/tests/ui/needless_for_each_fixable.stderr index 204cfa36b022..121669d15072 100644 --- a/tests/ui/needless_for_each_fixable.stderr +++ b/tests/ui/needless_for_each_fixable.stderr @@ -154,5 +154,22 @@ error: needless use of `for_each` LL | vec.iter().for_each(|v| println!("{v}")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for v in vec.iter() { println!("{v}"); }` -error: aborting due to 11 previous errors +error: needless use of `for_each` + --> tests/ui/needless_for_each_fixable.rs:155:5 + | +LL | / vec.iter().for_each(|elem| { +LL | | +LL | | println!("{elem}"); +LL | | }) + | |______^ + | +help: try + | +LL ~ for elem in vec.iter() { +LL + +LL + println!("{elem}"); +LL + } + | + +error: aborting due to 12 previous errors diff --git a/tests/ui/never_loop_iterator_reduction.rs b/tests/ui/never_loop_iterator_reduction.rs index 6b07b91db29a..27f1766b841d 100644 --- a/tests/ui/never_loop_iterator_reduction.rs +++ b/tests/ui/never_loop_iterator_reduction.rs @@ -1,8 +1,9 @@ //@no-rustfix #![warn(clippy::never_loop)] +#![expect(clippy::needless_return)] fn main() { - // diverging closure: should trigger + // diverging closure with no `return`: should trigger [0, 1].into_iter().for_each(|x| { //~^ never_loop @@ -14,4 +15,10 @@ fn main() { [0, 1].into_iter().for_each(|x| { let _ = x + 1; }); + + // `return` should NOT trigger even though it is diverging + [0, 1].into_iter().for_each(|x| { + println!("x = {x}"); + return; + }); } diff --git a/tests/ui/never_loop_iterator_reduction.stderr b/tests/ui/never_loop_iterator_reduction.stderr index b76ee283146c..92483c85432d 100644 --- a/tests/ui/never_loop_iterator_reduction.stderr +++ b/tests/ui/never_loop_iterator_reduction.stderr @@ -1,5 +1,5 @@ error: this iterator reduction never loops (closure always diverges) - --> tests/ui/never_loop_iterator_reduction.rs:6:5 + --> tests/ui/never_loop_iterator_reduction.rs:7:5 | LL | / [0, 1].into_iter().for_each(|x| { LL | | diff --git a/tests/ui/new_without_default.fixed b/tests/ui/new_without_default.fixed index 9a5e90b48065..f6591820feeb 100644 --- a/tests/ui/new_without_default.fixed +++ b/tests/ui/new_without_default.fixed @@ -409,3 +409,58 @@ mod issue15778 { } } } + +pub mod issue16255 { + use std::fmt::Display; + use std::marker::PhantomData; + + pub struct Foo { + marker: PhantomData, + } + + impl Default for Foo + where + T: Display, + T: Clone, + { + fn default() -> Self { + Self::new() + } + } + + impl Foo + where + T: Display, + { + pub fn new() -> Self + //~^ new_without_default + where + T: Clone, + { + Self { marker: PhantomData } + } + } + + pub struct Bar { + marker: PhantomData, + } + + impl Default for Bar + where + T: Clone, + { + fn default() -> Self { + Self::new() + } + } + + impl Bar { + pub fn new() -> Self + //~^ new_without_default + where + T: Clone, + { + Self { marker: PhantomData } + } + } +} diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index f7466aa32189..d3447f2e16b2 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -324,3 +324,39 @@ mod issue15778 { } } } + +pub mod issue16255 { + use std::fmt::Display; + use std::marker::PhantomData; + + pub struct Foo { + marker: PhantomData, + } + + impl Foo + where + T: Display, + { + pub fn new() -> Self + //~^ new_without_default + where + T: Clone, + { + Self { marker: PhantomData } + } + } + + pub struct Bar { + marker: PhantomData, + } + + impl Bar { + pub fn new() -> Self + //~^ new_without_default + where + T: Clone, + { + Self { marker: PhantomData } + } + } +} diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 0593dbb00fb6..6c0f73d13185 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -250,5 +250,58 @@ LL + } LL + } | -error: aborting due to 13 previous errors +error: you should consider adding a `Default` implementation for `Foo` + --> tests/ui/new_without_default.rs:340:9 + | +LL | / pub fn new() -> Self +LL | | +LL | | where +LL | | T: Clone, +LL | | { +LL | | Self { marker: PhantomData } +LL | | } + | |_________^ + | +help: try adding this + | +LL ~ impl Default for Foo +LL + where +LL + T: Display, +LL + T: Clone, +LL + { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL + +LL ~ impl Foo + | + +error: you should consider adding a `Default` implementation for `Bar` + --> tests/ui/new_without_default.rs:354:9 + | +LL | / pub fn new() -> Self +LL | | +LL | | where +LL | | T: Clone, +LL | | { +LL | | Self { marker: PhantomData } +LL | | } + | |_________^ + | +help: try adding this + | +LL ~ impl Default for Bar +LL + where +LL + T: Clone, +LL + { +LL + fn default() -> Self { +LL + Self::new() +LL + } +LL + } +LL + +LL ~ impl Bar { + | + +error: aborting due to 15 previous errors diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 2c5ee0245038..b8072932c4ea 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -1,5 +1,5 @@ #![feature(try_blocks)] -#![allow(clippy::unnecessary_wraps)] +#![allow(clippy::unnecessary_wraps, clippy::no_effect)] use std::sync::MutexGuard; @@ -500,3 +500,18 @@ mod issue14894 { Ok(()) } } + +fn wrongly_unmangled_macros() -> Option { + macro_rules! test_expr { + ($val:expr) => { + Some($val) + }; + } + + let x = test_expr!(42)?; + //~^^^ question_mark + Some(x); + + test_expr!(42)?; + test_expr!(42) +} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index b9ff9d1565b2..b320dcd4b0bc 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -1,5 +1,5 @@ #![feature(try_blocks)] -#![allow(clippy::unnecessary_wraps)] +#![allow(clippy::unnecessary_wraps, clippy::no_effect)] use std::sync::MutexGuard; @@ -615,3 +615,23 @@ mod issue14894 { Ok(()) } } + +fn wrongly_unmangled_macros() -> Option { + macro_rules! test_expr { + ($val:expr) => { + Some($val) + }; + } + + let Some(x) = test_expr!(42) else { + return None; + }; + //~^^^ question_mark + Some(x); + + if test_expr!(42).is_none() { + //~^ question_mark + return None; + } + test_expr!(42) +} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 9b2896328e66..d645c8830adc 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -333,5 +333,22 @@ LL | | return Err(reason); LL | | } | |_________^ help: replace it with: `result?;` -error: aborting due to 35 previous errors +error: this `let...else` may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:626:5 + | +LL | / let Some(x) = test_expr!(42) else { +LL | | return None; +LL | | }; + | |______^ help: replace it with: `let x = test_expr!(42)?;` + +error: this block may be rewritten with the `?` operator + --> tests/ui/question_mark.rs:632:5 + | +LL | / if test_expr!(42).is_none() { +LL | | +LL | | return None; +LL | | } + | |_____^ help: replace it with: `test_expr!(42)?;` + +error: aborting due to 37 previous errors diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed index 08903ef7fdda..b44009446640 100644 --- a/tests/ui/redundant_pattern_matching_option.fixed +++ b/tests/ui/redundant_pattern_matching_option.fixed @@ -195,3 +195,16 @@ fn issue16045() { } } } + +fn issue14989() { + macro_rules! x { + () => { + None:: + }; + } + + if x! {}.is_some() {}; + //~^ redundant_pattern_matching + while x! {}.is_some() {} + //~^ redundant_pattern_matching +} diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs index 95eff3f9ebf9..c13cf993e786 100644 --- a/tests/ui/redundant_pattern_matching_option.rs +++ b/tests/ui/redundant_pattern_matching_option.rs @@ -231,3 +231,16 @@ fn issue16045() { } } } + +fn issue14989() { + macro_rules! x { + () => { + None:: + }; + } + + if let Some(_) = (x! {}) {}; + //~^ redundant_pattern_matching + while let Some(_) = (x! {}) {} + //~^ redundant_pattern_matching +} diff --git a/tests/ui/redundant_pattern_matching_option.stderr b/tests/ui/redundant_pattern_matching_option.stderr index 6fd0c5a6f859..5c9edfd4c50a 100644 --- a/tests/ui/redundant_pattern_matching_option.stderr +++ b/tests/ui/redundant_pattern_matching_option.stderr @@ -236,5 +236,17 @@ error: redundant pattern matching, consider using `is_some()` LL | if let Some(_) = x.await { | -------^^^^^^^---------- help: try: `if x.await.is_some()` -error: aborting due to 33 previous errors +error: redundant pattern matching, consider using `is_some()` + --> tests/ui/redundant_pattern_matching_option.rs:242:12 + | +LL | if let Some(_) = (x! {}) {}; + | -------^^^^^^^---------- help: try: `if x! {}.is_some()` + +error: redundant pattern matching, consider using `is_some()` + --> tests/ui/redundant_pattern_matching_option.rs:244:15 + | +LL | while let Some(_) = (x! {}) {} + | ----------^^^^^^^---------- help: try: `while x! {}.is_some()` + +error: aborting due to 35 previous errors diff --git a/tests/ui/redundant_pattern_matching_result.fixed b/tests/ui/redundant_pattern_matching_result.fixed index 261d82fc35c8..8754d71aa629 100644 --- a/tests/ui/redundant_pattern_matching_result.fixed +++ b/tests/ui/redundant_pattern_matching_result.fixed @@ -165,3 +165,23 @@ fn issue10803() { // Don't lint let _ = matches!(x, Err(16)); } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + Ok::($val) + }; + } + + let _ = test_expr!(42).is_ok(); + + macro_rules! test_guard { + ($val:expr) => { + ($val + 1) > 0 + }; + } + + let x: Result = Ok(42); + let _ = x.is_ok() && test_guard!(42); + //~^ redundant_pattern_matching +} diff --git a/tests/ui/redundant_pattern_matching_result.rs b/tests/ui/redundant_pattern_matching_result.rs index 6cae4cc4b6b0..b83b02588fb6 100644 --- a/tests/ui/redundant_pattern_matching_result.rs +++ b/tests/ui/redundant_pattern_matching_result.rs @@ -205,3 +205,27 @@ fn issue10803() { // Don't lint let _ = matches!(x, Err(16)); } + +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($val:expr) => { + Ok::($val) + }; + } + + let _ = match test_expr!(42) { + //~^ redundant_pattern_matching + Ok(_) => true, + Err(_) => false, + }; + + macro_rules! test_guard { + ($val:expr) => { + ($val + 1) > 0 + }; + } + + let x: Result = Ok(42); + let _ = matches!(x, Ok(_) if test_guard!(42)); + //~^ redundant_pattern_matching +} diff --git a/tests/ui/redundant_pattern_matching_result.stderr b/tests/ui/redundant_pattern_matching_result.stderr index 7e7d27d07a7f..dda203b753c3 100644 --- a/tests/ui/redundant_pattern_matching_result.stderr +++ b/tests/ui/redundant_pattern_matching_result.stderr @@ -209,5 +209,22 @@ error: redundant pattern matching, consider using `is_err()` LL | let _ = matches!(x, Err(_)); | ^^^^^^^^^^^^^^^^^^^ help: try: `x.is_err()` -error: aborting due to 28 previous errors +error: redundant pattern matching, consider using `is_ok()` + --> tests/ui/redundant_pattern_matching_result.rs:216:13 + | +LL | let _ = match test_expr!(42) { + | _____________^ +LL | | +LL | | Ok(_) => true, +LL | | Err(_) => false, +LL | | }; + | |_____^ help: try: `test_expr!(42).is_ok()` + +error: redundant pattern matching, consider using `is_ok()` + --> tests/ui/redundant_pattern_matching_result.rs:229:13 + | +LL | let _ = matches!(x, Ok(_) if test_guard!(42)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_ok() && test_guard!(42)` + +error: aborting due to 30 previous errors diff --git a/tests/ui/single_range_in_vec_init_unfixable.rs b/tests/ui/single_range_in_vec_init_unfixable.rs new file mode 100644 index 000000000000..33378b386f34 --- /dev/null +++ b/tests/ui/single_range_in_vec_init_unfixable.rs @@ -0,0 +1,12 @@ +//@no-rustfix +#![warn(clippy::single_range_in_vec_init)] + +use std::ops::Range; + +fn issue16306(v: &[i32]) { + fn takes_range_slice(_: &[Range]) {} + + let len = v.len(); + takes_range_slice(&[0..len as i64]); + //~^ single_range_in_vec_init +} diff --git a/tests/ui/single_range_in_vec_init_unfixable.stderr b/tests/ui/single_range_in_vec_init_unfixable.stderr new file mode 100644 index 000000000000..b10af21ed21c --- /dev/null +++ b/tests/ui/single_range_in_vec_init_unfixable.stderr @@ -0,0 +1,16 @@ +error: an array of `Range` that is only one element + --> tests/ui/single_range_in_vec_init_unfixable.rs:10:24 + | +LL | takes_range_slice(&[0..len as i64]); + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::single-range-in-vec-init` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::single_range_in_vec_init)]` +help: if you wanted a `Vec` that contains the entire range, try + | +LL - takes_range_slice(&[0..len as i64]); +LL + takes_range_slice(&(0..len as i64).collect::>()); + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/str_to_string.fixed b/tests/ui/str_to_string.fixed index 2941c4dbd33d..8713c4f9bc86 100644 --- a/tests/ui/str_to_string.fixed +++ b/tests/ui/str_to_string.fixed @@ -8,3 +8,17 @@ fn main() { msg.to_owned(); //~^ str_to_string } + +fn issue16271(key: &[u8]) { + macro_rules! t { + ($e:expr) => { + match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {}", stringify!($e), e), + } + }; + } + + let _value = t!(str::from_utf8(key)).to_owned(); + //~^ str_to_string +} diff --git a/tests/ui/str_to_string.rs b/tests/ui/str_to_string.rs index 4c4d2bb18062..b81759e1037b 100644 --- a/tests/ui/str_to_string.rs +++ b/tests/ui/str_to_string.rs @@ -8,3 +8,17 @@ fn main() { msg.to_string(); //~^ str_to_string } + +fn issue16271(key: &[u8]) { + macro_rules! t { + ($e:expr) => { + match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {}", stringify!($e), e), + } + }; + } + + let _value = t!(str::from_utf8(key)).to_string(); + //~^ str_to_string +} diff --git a/tests/ui/str_to_string.stderr b/tests/ui/str_to_string.stderr index cb7b6b48843a..c0a38c8ebe46 100644 --- a/tests/ui/str_to_string.stderr +++ b/tests/ui/str_to_string.stderr @@ -13,5 +13,11 @@ error: `to_string()` called on a `&str` LL | msg.to_string(); | ^^^^^^^^^^^^^^^ help: try: `msg.to_owned()` -error: aborting due to 2 previous errors +error: `to_string()` called on a `&str` + --> tests/ui/str_to_string.rs:22:18 + | +LL | let _value = t!(str::from_utf8(key)).to_string(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `t!(str::from_utf8(key)).to_owned()` + +error: aborting due to 3 previous errors diff --git a/tests/ui/string_from_utf8_as_bytes.fixed b/tests/ui/string_from_utf8_as_bytes.fixed index 193217114d88..98fa3a4fcf70 100644 --- a/tests/ui/string_from_utf8_as_bytes.fixed +++ b/tests/ui/string_from_utf8_as_bytes.fixed @@ -1,6 +1,16 @@ #![warn(clippy::string_from_utf8_as_bytes)] +macro_rules! test_range { + ($start:expr, $end:expr) => { + $start..$end + }; +} + fn main() { let _ = Some(&"Hello World!"[6..11]); //~^ string_from_utf8_as_bytes + + let s = "Hello World!"; + let _ = Some(&s[test_range!(6, 11)]); + //~^ string_from_utf8_as_bytes } diff --git a/tests/ui/string_from_utf8_as_bytes.rs b/tests/ui/string_from_utf8_as_bytes.rs index 49beb19ee40f..6354d5376ad6 100644 --- a/tests/ui/string_from_utf8_as_bytes.rs +++ b/tests/ui/string_from_utf8_as_bytes.rs @@ -1,6 +1,16 @@ #![warn(clippy::string_from_utf8_as_bytes)] +macro_rules! test_range { + ($start:expr, $end:expr) => { + $start..$end + }; +} + fn main() { let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]); //~^ string_from_utf8_as_bytes + + let s = "Hello World!"; + let _ = std::str::from_utf8(&s.as_bytes()[test_range!(6, 11)]); + //~^ string_from_utf8_as_bytes } diff --git a/tests/ui/string_from_utf8_as_bytes.stderr b/tests/ui/string_from_utf8_as_bytes.stderr index 99c8d8ae4eab..bba9ec0caf19 100644 --- a/tests/ui/string_from_utf8_as_bytes.stderr +++ b/tests/ui/string_from_utf8_as_bytes.stderr @@ -1,5 +1,5 @@ error: calling a slice of `as_bytes()` with `from_utf8` should be not necessary - --> tests/ui/string_from_utf8_as_bytes.rs:4:13 + --> tests/ui/string_from_utf8_as_bytes.rs:10:13 | LL | let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(&"Hello World!"[6..11])` @@ -7,5 +7,11 @@ LL | let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]); = note: `-D clippy::string-from-utf8-as-bytes` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::string_from_utf8_as_bytes)]` -error: aborting due to 1 previous error +error: calling a slice of `as_bytes()` with `from_utf8` should be not necessary + --> tests/ui/string_from_utf8_as_bytes.rs:14:13 + | +LL | let _ = std::str::from_utf8(&s.as_bytes()[test_range!(6, 11)]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(&s[test_range!(6, 11)])` + +error: aborting due to 2 previous errors diff --git a/tests/ui/transmuting_null.rs b/tests/ui/transmuting_null.rs index 0d3b26673452..00aa35dff803 100644 --- a/tests/ui/transmuting_null.rs +++ b/tests/ui/transmuting_null.rs @@ -37,8 +37,19 @@ fn transmute_const_int() { } } +fn transumute_single_expr_blocks() { + unsafe { + let _: &u64 = std::mem::transmute({ 0 as *const u64 }); + //~^ transmuting_null + + let _: &u64 = std::mem::transmute(const { u64::MIN as *const u64 }); + //~^ transmuting_null + } +} + fn main() { one_liners(); transmute_const(); transmute_const_int(); + transumute_single_expr_blocks(); } diff --git a/tests/ui/transmuting_null.stderr b/tests/ui/transmuting_null.stderr index ed7c3396a243..e1de391813bd 100644 --- a/tests/ui/transmuting_null.stderr +++ b/tests/ui/transmuting_null.stderr @@ -25,5 +25,17 @@ error: transmuting a known null pointer into a reference LL | let _: &u64 = std::mem::transmute(u64::MIN as *const u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: transmuting a known null pointer into a reference + --> tests/ui/transmuting_null.rs:42:23 + | +LL | let _: &u64 = std::mem::transmute({ 0 as *const u64 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmuting a known null pointer into a reference + --> tests/ui/transmuting_null.rs:45:23 + | +LL | let _: &u64 = std::mem::transmute(const { u64::MIN as *const u64 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors diff --git a/tests/ui/unnecessary_fold.fixed b/tests/ui/unnecessary_fold.fixed index c3eeafbc39cd..d51359349cb9 100644 --- a/tests/ui/unnecessary_fold.fixed +++ b/tests/ui/unnecessary_fold.fixed @@ -178,4 +178,15 @@ fn issue10000() { } } +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($e:expr) => { + ($e + 1) > 2 + }; + } + + let _ = (0..3).any(|x| test_expr!(x)); + //~^ unnecessary_fold +} + fn main() {} diff --git a/tests/ui/unnecessary_fold.rs b/tests/ui/unnecessary_fold.rs index 6ab41a942625..c6eb7157ab12 100644 --- a/tests/ui/unnecessary_fold.rs +++ b/tests/ui/unnecessary_fold.rs @@ -178,4 +178,15 @@ fn issue10000() { } } +fn wrongly_unmangled_macros() { + macro_rules! test_expr { + ($e:expr) => { + ($e + 1) > 2 + }; + } + + let _ = (0..3).fold(false, |acc: bool, x| acc || test_expr!(x)); + //~^ unnecessary_fold +} + fn main() {} diff --git a/tests/ui/unnecessary_fold.stderr b/tests/ui/unnecessary_fold.stderr index bb8aa7e18d34..560427a681a9 100644 --- a/tests/ui/unnecessary_fold.stderr +++ b/tests/ui/unnecessary_fold.stderr @@ -4,6 +4,7 @@ error: this `.fold` can be written more succinctly using another method LL | let _ = (0..3).fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` | + = note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects = note: `-D clippy::unnecessary-fold` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_fold)]` @@ -21,6 +22,8 @@ error: this `.fold` can be written more succinctly using another method | LL | let _ = (0..3).fold(true, |acc, x| acc && x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `all(|x| x > 2)` + | + = note: the `all` method is short circuiting and may change the program semantics if the iterator has side effects error: this `.fold` can be written more succinctly using another method --> tests/ui/unnecessary_fold.rs:24:25 @@ -63,12 +66,16 @@ error: this `.fold` can be written more succinctly using another method | LL | let _: bool = (0..3).map(|x| 2 * x).fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + | + = note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects error: this `.fold` can be written more succinctly using another method --> tests/ui/unnecessary_fold.rs:110:10 | LL | .fold(false, |acc, x| acc || x > 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| x > 2)` + | + = note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects error: this `.fold` can be written more succinctly using another method --> tests/ui/unnecessary_fold.rs:123:33 @@ -196,5 +203,13 @@ error: this `.fold` can be written more succinctly using another method LL | (0..3).fold(1, |acc, x| acc * x) | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `product::()` -error: aborting due to 32 previous errors +error: this `.fold` can be written more succinctly using another method + --> tests/ui/unnecessary_fold.rs:188:20 + | +LL | let _ = (0..3).fold(false, |acc: bool, x| acc || test_expr!(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `any(|x| test_expr!(x))` + | + = note: the `any` method is short circuiting and may change the program semantics if the iterator has side effects + +error: aborting due to 33 previous errors diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index 316eac0b58b7..590359bc1ad2 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -681,3 +681,12 @@ fn issue14833() { let mut s = HashSet::<&String>::new(); s.remove(&"hello".to_owned()); } + +#[allow(clippy::redundant_clone)] +fn issue16351() { + fn take(_: impl AsRef) {} + + let dot = "."; + take(&format!("ouch{dot}")); + //~^ unnecessary_to_owned +} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index f2dbd1db3c9f..d1e3e6497c0a 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -681,3 +681,12 @@ fn issue14833() { let mut s = HashSet::<&String>::new(); s.remove(&"hello".to_owned()); } + +#[allow(clippy::redundant_clone)] +fn issue16351() { + fn take(_: impl AsRef) {} + + let dot = "."; + take(format!("ouch{dot}").to_string()); + //~^ unnecessary_to_owned +} diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 6c52be839301..50e3d5eb2195 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -550,5 +550,11 @@ error: unnecessary use of `to_vec` LL | s.remove(&(&["b"]).to_vec()); | ^^^^^^^^^^^^^^^^^^ help: replace it with: `(&["b"]).as_slice()` -error: aborting due to 82 previous errors +error: unnecessary use of `to_string` + --> tests/ui/unnecessary_to_owned.rs:690:10 + | +LL | take(format!("ouch{dot}").to_string()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `&format!("ouch{dot}")` + +error: aborting due to 83 previous errors diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index adf5e58d9a1a..4832e922fa8e 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -1,4 +1,5 @@ #![deny(clippy::useless_conversion)] +#![allow(clippy::into_iter_on_ref)] #![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] @@ -131,6 +132,15 @@ fn main() { dont_lint_into_iter_on_copy_iter(); dont_lint_into_iter_on_static_copy_iter(); + { + // triggers the IntoIterator trait + fn consume(_: impl IntoIterator) {} + + // Should suggest `*items` instead of `&**items` + let items = &&[1, 2, 3]; + consume(*items); //~ useless_conversion + } + let _: String = "foo".into(); let _: String = From::from("foo"); let _ = String::from("foo"); diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index d95fe49e2e2b..6ef1f93a5606 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -1,4 +1,5 @@ #![deny(clippy::useless_conversion)] +#![allow(clippy::into_iter_on_ref)] #![allow(clippy::needless_ifs, clippy::unnecessary_wraps, unused)] // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint #![allow(static_mut_refs)] @@ -131,6 +132,15 @@ fn main() { dont_lint_into_iter_on_copy_iter(); dont_lint_into_iter_on_static_copy_iter(); + { + // triggers the IntoIterator trait + fn consume(_: impl IntoIterator) {} + + // Should suggest `*items` instead of `&**items` + let items = &&[1, 2, 3]; + consume(items.into_iter()); //~ useless_conversion + } + let _: String = "foo".into(); let _: String = From::from("foo"); let _ = String::from("foo"); diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr index 052c664f6f2e..d28b7a5cbfb6 100644 --- a/tests/ui/useless_conversion.stderr +++ b/tests/ui/useless_conversion.stderr @@ -1,5 +1,5 @@ error: useless conversion to the same type: `T` - --> tests/ui/useless_conversion.rs:9:13 + --> tests/ui/useless_conversion.rs:10:13 | LL | let _ = T::from(val); | ^^^^^^^^^^^^ help: consider removing `T::from()`: `val` @@ -11,115 +11,132 @@ LL | #![deny(clippy::useless_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: useless conversion to the same type: `T` - --> tests/ui/useless_conversion.rs:11:5 + --> tests/ui/useless_conversion.rs:12:5 | LL | val.into() | ^^^^^^^^^^ help: consider removing `.into()`: `val` error: useless conversion to the same type: `i32` - --> tests/ui/useless_conversion.rs:24:22 + --> tests/ui/useless_conversion.rs:25:22 | LL | let _: i32 = 0i32.into(); | ^^^^^^^^^^^ help: consider removing `.into()`: `0i32` error: useless conversion to the same type: `std::str::Lines<'_>` - --> tests/ui/useless_conversion.rs:55:22 + --> tests/ui/useless_conversion.rs:56:22 | LL | if Some("ok") == lines.into_iter().next() {} | ^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `lines` error: useless conversion to the same type: `std::str::Lines<'_>` - --> tests/ui/useless_conversion.rs:61:21 + --> tests/ui/useless_conversion.rs:62:21 | LL | let mut lines = text.lines().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `text.lines()` error: useless conversion to the same type: `std::str::Lines<'_>` - --> tests/ui/useless_conversion.rs:68:22 + --> tests/ui/useless_conversion.rs:69:22 | LL | if Some("ok") == text.lines().into_iter().next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `text.lines()` error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:75:13 + --> tests/ui/useless_conversion.rs:76:13 | LL | let _ = NUMBERS.into_iter().next(); | ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `NUMBERS` error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:81:17 + --> tests/ui/useless_conversion.rs:82:17 | LL | let mut n = NUMBERS.into_iter(); | ^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `NUMBERS` +error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` + --> tests/ui/useless_conversion.rs:141:17 + | +LL | consume(items.into_iter()); + | ^^^^^^^^^^^^^^^^^ + | +note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` + --> tests/ui/useless_conversion.rs:137:28 + | +LL | fn consume(_: impl IntoIterator) {} + | ^^^^^^^^^^^^ +help: consider removing the `.into_iter()` + | +LL - consume(items.into_iter()); +LL + consume(*items); + | + error: useless conversion to the same type: `std::string::String` - --> tests/ui/useless_conversion.rs:144:21 + --> tests/ui/useless_conversion.rs:154:21 | LL | let _: String = "foo".to_string().into(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()` error: useless conversion to the same type: `std::string::String` - --> tests/ui/useless_conversion.rs:146:21 + --> tests/ui/useless_conversion.rs:156:21 | LL | let _: String = From::from("foo".to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()` error: useless conversion to the same type: `std::string::String` - --> tests/ui/useless_conversion.rs:148:13 + --> tests/ui/useless_conversion.rs:158:13 | LL | let _ = String::from("foo".to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()` error: useless conversion to the same type: `std::string::String` - --> tests/ui/useless_conversion.rs:150:13 + --> tests/ui/useless_conversion.rs:160:13 | LL | let _ = String::from(format!("A: {:04}", 123)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `format!("A: {:04}", 123)` error: useless conversion to the same type: `std::str::Lines<'_>` - --> tests/ui/useless_conversion.rs:152:13 + --> tests/ui/useless_conversion.rs:162:13 | LL | let _ = "".lines().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `"".lines()` error: useless conversion to the same type: `std::vec::IntoIter` - --> tests/ui/useless_conversion.rs:154:13 + --> tests/ui/useless_conversion.rs:164:13 | LL | let _ = vec![1, 2, 3].into_iter().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2, 3].into_iter()` error: useless conversion to the same type: `std::string::String` - --> tests/ui/useless_conversion.rs:156:21 + --> tests/ui/useless_conversion.rs:166:21 | LL | let _: String = format!("Hello {}", "world").into(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `format!("Hello {}", "world")` error: useless conversion to the same type: `i32` - --> tests/ui/useless_conversion.rs:162:13 + --> tests/ui/useless_conversion.rs:172:13 | LL | let _ = i32::from(a + b) * 3; | ^^^^^^^^^^^^^^^^ help: consider removing `i32::from()`: `(a + b)` error: useless conversion to the same type: `Foo<'a'>` - --> tests/ui/useless_conversion.rs:169:23 + --> tests/ui/useless_conversion.rs:179:23 | LL | let _: Foo<'a'> = s2.into(); | ^^^^^^^^^ help: consider removing `.into()`: `s2` error: useless conversion to the same type: `Foo<'a'>` - --> tests/ui/useless_conversion.rs:172:13 + --> tests/ui/useless_conversion.rs:182:13 | LL | let _ = Foo::<'a'>::from(s3); | ^^^^^^^^^^^^^^^^^^^^ help: consider removing `Foo::<'a'>::from()`: `s3` error: useless conversion to the same type: `std::vec::IntoIter>` - --> tests/ui/useless_conversion.rs:175:13 + --> tests/ui/useless_conversion.rs:185:13 | LL | let _ = vec![s4, s4, s4].into_iter().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![s4, s4, s4].into_iter()` error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:208:7 + --> tests/ui/useless_conversion.rs:218:7 | LL | b(vec![1, 2].into_iter()); | ^^^^^^^^^^------------ @@ -127,13 +144,13 @@ LL | b(vec![1, 2].into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:198:13 + --> tests/ui/useless_conversion.rs:208:13 | LL | fn b>(_: T) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:210:7 + --> tests/ui/useless_conversion.rs:220:7 | LL | c(vec![1, 2].into_iter()); | ^^^^^^^^^^------------ @@ -141,13 +158,13 @@ LL | c(vec![1, 2].into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:199:18 + --> tests/ui/useless_conversion.rs:209:18 | LL | fn c(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:212:7 + --> tests/ui/useless_conversion.rs:222:7 | LL | d(vec![1, 2].into_iter()); | ^^^^^^^^^^------------ @@ -155,13 +172,13 @@ LL | d(vec![1, 2].into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:202:12 + --> tests/ui/useless_conversion.rs:212:12 | LL | T: IntoIterator, | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:216:7 + --> tests/ui/useless_conversion.rs:226:7 | LL | b(vec![1, 2].into_iter().into_iter()); | ^^^^^^^^^^------------------------ @@ -169,13 +186,13 @@ LL | b(vec![1, 2].into_iter().into_iter()); | help: consider removing the `.into_iter()`s | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:198:13 + --> tests/ui/useless_conversion.rs:208:13 | LL | fn b>(_: T) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:218:7 + --> tests/ui/useless_conversion.rs:228:7 | LL | b(vec![1, 2].into_iter().into_iter().into_iter()); | ^^^^^^^^^^------------------------------------ @@ -183,13 +200,13 @@ LL | b(vec![1, 2].into_iter().into_iter().into_iter()); | help: consider removing the `.into_iter()`s | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:198:13 + --> tests/ui/useless_conversion.rs:208:13 | LL | fn b>(_: T) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:265:24 + --> tests/ui/useless_conversion.rs:275:24 | LL | foo2::([1, 2, 3].into_iter()); | ^^^^^^^^^------------ @@ -197,13 +214,13 @@ LL | foo2::([1, 2, 3].into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:244:12 + --> tests/ui/useless_conversion.rs:254:12 | LL | I: IntoIterator + Helper, | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:274:14 + --> tests/ui/useless_conversion.rs:284:14 | LL | foo3([1, 2, 3].into_iter()); | ^^^^^^^^^------------ @@ -211,13 +228,13 @@ LL | foo3([1, 2, 3].into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:253:12 + --> tests/ui/useless_conversion.rs:263:12 | LL | I: IntoIterator, | ^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:284:16 + --> tests/ui/useless_conversion.rs:294:16 | LL | S1.foo([1, 2].into_iter()); | ^^^^^^------------ @@ -225,13 +242,13 @@ LL | S1.foo([1, 2].into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:281:27 + --> tests/ui/useless_conversion.rs:291:27 | LL | pub fn foo(&self, _: I) {} | ^^^^^^^^^^^^ error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:304:44 + --> tests/ui/useless_conversion.rs:314:44 | LL | v0.into_iter().interleave_shortest(v1.into_iter()); | ^^------------ @@ -239,67 +256,67 @@ LL | v0.into_iter().interleave_shortest(v1.into_iter()); | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:291:20 + --> tests/ui/useless_conversion.rs:301:20 | LL | J: IntoIterator, | ^^^^^^^^^^^^ error: useless conversion to the same type: `()` - --> tests/ui/useless_conversion.rs:332:58 + --> tests/ui/useless_conversion.rs:342:58 | LL | let _: Result<(), std::io::Error> = test_issue_3913().map(Into::into); | ^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `std::io::Error` - --> tests/ui/useless_conversion.rs:335:58 + --> tests/ui/useless_conversion.rs:345:58 | LL | let _: Result<(), std::io::Error> = test_issue_3913().map_err(Into::into); | ^^^^^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `()` - --> tests/ui/useless_conversion.rs:338:58 + --> tests/ui/useless_conversion.rs:348:58 | LL | let _: Result<(), std::io::Error> = test_issue_3913().map(From::from); | ^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `std::io::Error` - --> tests/ui/useless_conversion.rs:341:58 + --> tests/ui/useless_conversion.rs:351:58 | LL | let _: Result<(), std::io::Error> = test_issue_3913().map_err(From::from); | ^^^^^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `()` - --> tests/ui/useless_conversion.rs:345:31 + --> tests/ui/useless_conversion.rs:355:31 | LL | let _: ControlFlow<()> = c.map_break(Into::into); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `()` - --> tests/ui/useless_conversion.rs:349:31 + --> tests/ui/useless_conversion.rs:359:31 | LL | let _: ControlFlow<()> = c.map_continue(Into::into); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `u32` - --> tests/ui/useless_conversion.rs:363:41 + --> tests/ui/useless_conversion.rs:373:41 | LL | let _: Vec = [1u32].into_iter().map(Into::into).collect(); | ^^^^^^^^^^^^^^^^ help: consider removing error: useless conversion to the same type: `T` - --> tests/ui/useless_conversion.rs:374:18 + --> tests/ui/useless_conversion.rs:384:18 | LL | x.into_iter().map(Into::into).collect() | ^^^^^^^^^^^^^^^^ help: consider removing error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:390:29 + --> tests/ui/useless_conversion.rs:400:29 | LL | takes_into_iter(self.my_field.into_iter()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:379:32 + --> tests/ui/useless_conversion.rs:389:32 | LL | fn takes_into_iter(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -310,13 +327,13 @@ LL + takes_into_iter(&self.my_field); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:398:29 + --> tests/ui/useless_conversion.rs:408:29 | LL | takes_into_iter(self.my_field.into_iter()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:379:32 + --> tests/ui/useless_conversion.rs:389:32 | LL | fn takes_into_iter(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -327,13 +344,13 @@ LL + takes_into_iter(&mut self.my_field); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:407:29 + --> tests/ui/useless_conversion.rs:417:29 | LL | takes_into_iter(self.my_field.into_iter()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:379:32 + --> tests/ui/useless_conversion.rs:389:32 | LL | fn takes_into_iter(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -344,13 +361,13 @@ LL + takes_into_iter(*self.my_field); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:416:29 + --> tests/ui/useless_conversion.rs:426:29 | LL | takes_into_iter(self.my_field.into_iter()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:379:32 + --> tests/ui/useless_conversion.rs:389:32 | LL | fn takes_into_iter(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -361,13 +378,13 @@ LL + takes_into_iter(&*self.my_field); | error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` - --> tests/ui/useless_conversion.rs:425:29 + --> tests/ui/useless_conversion.rs:435:29 | LL | takes_into_iter(self.my_field.into_iter()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` - --> tests/ui/useless_conversion.rs:379:32 + --> tests/ui/useless_conversion.rs:389:32 | LL | fn takes_into_iter(_: impl IntoIterator) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -378,22 +395,22 @@ LL + takes_into_iter(&mut *self.my_field); | error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:440:5 + --> tests/ui/useless_conversion.rs:450:5 | LL | R.into_iter().for_each(|_x| {}); | ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R` error: useless conversion to the same type: `std::ops::Range` - --> tests/ui/useless_conversion.rs:442:13 + --> tests/ui/useless_conversion.rs:452:13 | LL | let _ = R.into_iter().map(|_x| 0); | ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R` error: useless conversion to the same type: `std::slice::Iter<'_, i32>` - --> tests/ui/useless_conversion.rs:453:14 + --> tests/ui/useless_conversion.rs:463:14 | LL | for _ in mac!(iter [1, 2]).into_iter() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `mac!(iter [1, 2])` -error: aborting due to 44 previous errors +error: aborting due to 45 previous errors diff --git a/triagebot.toml b/triagebot.toml index 5f637205fa65..09dec7675e7e 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -63,7 +63,6 @@ users_on_vacation = [ "Alexendoo", "y21", "blyxyas", - "samueltardieu", ] [assign.owners] From 7c3cc4f3ef6d936a11f19857984b06baa1a04d10 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 8 Jan 2026 19:08:01 +0100 Subject: [PATCH 042/273] Make Clippy compile with `ConstArgKind::Tup()` --- clippy_lints/src/large_include_file.rs | 1 - clippy_lints/src/utils/author.rs | 1 + clippy_utils/src/consts.rs | 1 + clippy_utils/src/hir_utils.rs | 8 ++++++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index d77e0beeaf4c..54599499515e 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,4 +1,3 @@ -use rustc_ast::AttrItemKind; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 58b153f06545..acea701b2e83 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -323,6 +323,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { ConstArgKind::TupleCall(..) => chain!(self, "let ConstArgKind::TupleCall(..) = {const_arg}.kind"), ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"), ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"), + ConstArgKind::Tup(..) => chain!(self, "let ConstArgKind::Tup(..) = {const_arg}.kind"), ConstArgKind::Literal(..) => chain!(self, "let ConstArgKind::Literal(..) = {const_arg}.kind") } } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 334cc6bb5d55..e4f76cf4ed57 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1141,6 +1141,7 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), ConstArgKind::Struct(..) + | ConstArgKind::Tup(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 73b1cbb21548..82a12fc51c9a 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -666,6 +666,8 @@ impl HirEqInterExpr<'_, '_, '_> { } match (&left.kind, &right.kind) { + (ConstArgKind::Tup(l_t), ConstArgKind::Tup(r_t)) => + l_t.len() == r_t.len() && l_t.iter().zip(*r_t).all(|(l_c, r_c)| self.eq_const_arg(*l_c, *r_c)), (ConstArgKind::Path(l_p), ConstArgKind::Path(r_p)) => self.eq_qpath(l_p, r_p), (ConstArgKind::Anon(l_an), ConstArgKind::Anon(r_an)) => self.eq_body(l_an.body, r_an.body), (ConstArgKind::Infer(..), ConstArgKind::Infer(..)) => true, @@ -695,6 +697,7 @@ impl HirEqInterExpr<'_, '_, '_> { // Use explicit match for now since ConstArg is undergoing flux. ( ConstArgKind::Path(..) + | ConstArgKind::Tup(..) | ConstArgKind::Anon(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Tup(..) @@ -1561,6 +1564,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { fn hash_const_arg(&mut self, const_arg: &ConstArg<'_>) { match &const_arg.kind { + ConstArgKind::Tup(tup) => { + for arg in *tup { + self.hash_const_arg(*arg); + } + }, ConstArgKind::Path(path) => self.hash_qpath(path), ConstArgKind::Anon(anon) => self.hash_body(anon.body), ConstArgKind::Struct(path, inits) => { From 697e9e2aacecaa6a610ab310b9af54a732903d21 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 9 Jan 2026 11:59:23 +0100 Subject: [PATCH 043/273] Further Clippy fixes for Tup/Literal ConstArgKind --- clippy_utils/src/consts.rs | 1 + clippy_utils/src/hir_utils.rs | 23 +++++------------------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index e4f76cf4ed57..538f1fd2628c 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1142,6 +1142,7 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), ConstArgKind::Struct(..) | ConstArgKind::Tup(..) + | ConstArgKind::Literal(..) | ConstArgKind::TupleCall(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 82a12fc51c9a..0bb641568879 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -666,8 +666,9 @@ impl HirEqInterExpr<'_, '_, '_> { } match (&left.kind, &right.kind) { - (ConstArgKind::Tup(l_t), ConstArgKind::Tup(r_t)) => - l_t.len() == r_t.len() && l_t.iter().zip(*r_t).all(|(l_c, r_c)| self.eq_const_arg(*l_c, *r_c)), + (ConstArgKind::Tup(l_t), ConstArgKind::Tup(r_t)) => { + l_t.len() == r_t.len() && l_t.iter().zip(*r_t).all(|(l_c, r_c)| self.eq_const_arg(*l_c, *r_c)) + }, (ConstArgKind::Path(l_p), ConstArgKind::Path(r_p)) => self.eq_qpath(l_p, r_p), (ConstArgKind::Anon(l_an), ConstArgKind::Anon(r_an)) => self.eq_body(l_an.body, r_an.body), (ConstArgKind::Infer(..), ConstArgKind::Infer(..)) => true, @@ -684,23 +685,14 @@ impl HirEqInterExpr<'_, '_, '_> { .iter() .zip(*args_b) .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) - } - (ConstArgKind::Tup(args_a), ConstArgKind::Tup(args_b)) => { - args_a - .iter() - .zip(*args_b) - .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) - }, - (ConstArgKind::Literal(kind_l), ConstArgKind::Literal(kind_r)) => { - kind_l == kind_r }, + (ConstArgKind::Literal(kind_l), ConstArgKind::Literal(kind_r)) => kind_l == kind_r, // Use explicit match for now since ConstArg is undergoing flux. ( ConstArgKind::Path(..) | ConstArgKind::Tup(..) | ConstArgKind::Anon(..) | ConstArgKind::TupleCall(..) - | ConstArgKind::Tup(..) | ConstArgKind::Infer(..) | ConstArgKind::Struct(..) | ConstArgKind::Literal(..) @@ -1583,13 +1575,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_const_arg(arg); } }, - ConstArgKind::Tup(args) => { - for arg in *args { - self.hash_const_arg(arg); - } - }, ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {}, - ConstArgKind::Literal(lit) => lit.hash(&mut self.s) + ConstArgKind::Literal(lit) => lit.hash(&mut self.s), } } From b022edce934b2fb939f876ccb78c1fb8b5bd6349 Mon Sep 17 00:00:00 2001 From: Wonko Date: Fri, 9 Jan 2026 15:17:06 +0100 Subject: [PATCH 044/273] add test for unstacking cognitive_complexity= attribute --- tests/ui/cognitive_complexity.rs | 25 +++++++++++++++++++++++++ tests/ui/cognitive_complexity.stderr | 18 +++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/ui/cognitive_complexity.rs b/tests/ui/cognitive_complexity.rs index 8080c6775e0b..3945028f8271 100644 --- a/tests/ui/cognitive_complexity.rs +++ b/tests/ui/cognitive_complexity.rs @@ -472,3 +472,28 @@ mod issue14422 { return; } } + +#[clippy::cognitive_complexity = "1"] +mod attribute_stacking { + fn bad() { + //~^ cognitive_complexity + if true { + println!("a"); + } + } + + #[clippy::cognitive_complexity = "2"] + fn ok() { + if true { + println!("a"); + } + } + + // should revert to cognitive_complexity = "1" + fn bad_again() { + //~^ cognitive_complexity + if true { + println!("a"); + } + } +} diff --git a/tests/ui/cognitive_complexity.stderr b/tests/ui/cognitive_complexity.stderr index 67ef4e5655bd..e5f54a37229d 100644 --- a/tests/ui/cognitive_complexity.stderr +++ b/tests/ui/cognitive_complexity.stderr @@ -176,5 +176,21 @@ LL | fn bar() { | = help: you could split it up into multiple smaller functions -error: aborting due to 22 previous errors +error: the function has a cognitive complexity of (2/1) + --> tests/ui/cognitive_complexity.rs:478:8 + | +LL | fn bad() { + | ^^^ + | + = help: you could split it up into multiple smaller functions + +error: the function has a cognitive complexity of (2/1) + --> tests/ui/cognitive_complexity.rs:493:8 + | +LL | fn bad_again() { + | ^^^^^^^^^ + | + = help: you could split it up into multiple smaller functions + +error: aborting due to 24 previous errors From 13b82eb615c68c71baf85ea8dcfbcab51a1dce1a Mon Sep 17 00:00:00 2001 From: Wonko Date: Fri, 9 Jan 2026 13:43:55 +0100 Subject: [PATCH 045/273] Fix LimitStack::pop_attrs in release mode The `LimitStack::pop_attrs` function used to pop from the stack in `debug_assert_eq!` and check the value. The `pop` operation was therefore only executed in debug builds, leading to an unbalanced stack in release builds when attributes were present. This change ensures the `pop` operation is always executed, by moving it out of the debug-only assertion. The assertion is kept for debug builds. changelog: fix unbalanced stack in attributes --- clippy_utils/src/attrs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 32f6cb4fd5e9..56490cfc8b65 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -159,7 +159,10 @@ impl LimitStack { } pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val))); + parse_attrs(sess, attrs, name, |val| { + let popped = stack.pop(); + debug_assert_eq!(popped, Some(val)); + }); } } From 0946c867a86cf3c830dbbd7073d8df7f71b3149b Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Wed, 17 Dec 2025 16:12:22 +0100 Subject: [PATCH 046/273] Add new `duration_suboptimal_units` lint --- CHANGELOG.md | 1 + clippy_dev/src/serve.rs | 2 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/duration_suboptimal_units.rs | 204 ++++++++++++++++++ clippy_lints/src/lib.rs | 6 +- clippy_utils/src/msrvs.rs | 6 +- clippy_utils/src/sym.rs | 9 + tests/ui/duration_suboptimal_units.fixed | 91 ++++++++ tests/ui/duration_suboptimal_units.rs | 91 ++++++++ tests/ui/duration_suboptimal_units.stderr | 152 +++++++++++++ ...duration_suboptimal_units_days_weeks.fixed | 17 ++ .../duration_suboptimal_units_days_weeks.rs | 17 ++ ...uration_suboptimal_units_days_weeks.stderr | 40 ++++ 13 files changed, 633 insertions(+), 4 deletions(-) create mode 100644 clippy_lints/src/duration_suboptimal_units.rs create mode 100644 tests/ui/duration_suboptimal_units.fixed create mode 100644 tests/ui/duration_suboptimal_units.rs create mode 100644 tests/ui/duration_suboptimal_units.stderr create mode 100644 tests/ui/duration_suboptimal_units_days_weeks.fixed create mode 100644 tests/ui/duration_suboptimal_units_days_weeks.rs create mode 100644 tests/ui/duration_suboptimal_units_days_weeks.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d793489be2..4cda8003b05c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6406,6 +6406,7 @@ Released 2018-09-13 [`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod [`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument [`duplicated_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes +[`duration_suboptimal_units`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_suboptimal_units [`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec [`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute [`elidable_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#elidable_lifetime_names diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index d9e018133813..b99289672420 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -54,7 +54,7 @@ pub fn run(port: u16, lint: Option) -> ! { } // Delay to avoid updating the metadata too aggressively. - thread::sleep(Duration::from_millis(1000)); + thread::sleep(Duration::from_secs(1)); } } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 6b68940c6423..ef9da84c4407 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -135,6 +135,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::drop_forget_ref::FORGET_NON_DROP_INFO, crate::drop_forget_ref::MEM_FORGET_INFO, crate::duplicate_mod::DUPLICATE_MOD_INFO, + crate::duration_suboptimal_units::DURATION_SUBOPTIMAL_UNITS_INFO, crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, crate::empty_enums::EMPTY_ENUMS_INFO, diff --git a/clippy_lints/src/duration_suboptimal_units.rs b/clippy_lints/src/duration_suboptimal_units.rs new file mode 100644 index 000000000000..8140585b70d3 --- /dev/null +++ b/clippy_lints/src/duration_suboptimal_units.rs @@ -0,0 +1,204 @@ +use std::ops::ControlFlow; + +use clippy_config::Conf; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::res::MaybeDef; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath, RustcVersion}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::TyCtxt; +use rustc_session::impl_lint_pass; +use rustc_span::Symbol; + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for instances where a `std::time::Duration` is constructed using a smaller time unit + /// when the value could be expressed more clearly using a larger unit. + /// + /// ### Why is this bad? + /// + /// Using a smaller unit for a duration that is evenly divisible by a larger unit reduces + /// readability. Readers have to mentally convert values, which can be error-prone and makes + /// the code less clear. + /// + /// ### Example + /// ``` + /// use std::time::Duration; + /// + /// let dur = Duration::from_millis(5_000); + /// let dur = Duration::from_secs(180); + /// let dur = Duration::from_mins(10 * 60); + /// ``` + /// + /// Use instead: + /// ``` + /// use std::time::Duration; + /// + /// let dur = Duration::from_secs(5); + /// let dur = Duration::from_mins(3); + /// let dur = Duration::from_hours(10); + /// ``` + #[clippy::version = "1.95.0"] + pub DURATION_SUBOPTIMAL_UNITS, + pedantic, + "constructing a `Duration` using a smaller unit when a larger unit would be more readable" +} + +impl_lint_pass!(DurationSuboptimalUnits => [DURATION_SUBOPTIMAL_UNITS]); + +pub struct DurationSuboptimalUnits { + msrv: Msrv, + units: Vec, +} + +impl DurationSuboptimalUnits { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { + // The order of the units matters, as they are walked top to bottom + let mut units = UNITS.to_vec(); + if tcx.features().enabled(sym::duration_constructors) { + units.extend(EXTENDED_UNITS); + } + Self { msrv: conf.msrv, units } + } +} + +impl LateLintPass<'_> for DurationSuboptimalUnits { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + if !expr.span.in_external_macro(cx.sess().source_map()) + // Check if a function on std::time::Duration is called + && let ExprKind::Call(func, [arg]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(func_ty, func_name)) = func.kind + && cx + .typeck_results() + .node_type(func_ty.hir_id) + .is_diag_item(cx, sym::Duration) + // We intentionally don't want to evaluate referenced constants, as we don't want to + // recommend a literal value over using constants: + // + // let dur = Duration::from_secs(SIXTY); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::from_mins(1)` + && let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) + && let value = u64::try_from(value).expect("All Duration::from_ constructors take a u64") + // There is no need to promote e.g. 0 seconds to 0 hours + && value != 0 + && let Some((promoted_constructor, promoted_value)) = self.promote(cx, func_name.ident.name, value) + { + span_lint_and_then( + cx, + DURATION_SUBOPTIMAL_UNITS, + expr.span, + "constructing a `Duration` using a smaller unit when a larger unit would be more readable", + |diag| { + let suggestions = vec![ + (func_name.ident.span, promoted_constructor.to_string()), + (arg.span, promoted_value.to_string()), + ]; + diag.multipart_suggestion_verbose( + format!("try using {promoted_constructor}"), + suggestions, + Applicability::MachineApplicable, + ); + }, + ); + } + } +} + +impl DurationSuboptimalUnits { + /// Tries to promote the given constructor and value to a bigger time unit and returns the + /// promoted constructor name and value. + /// + /// Returns [`None`] in case no promotion could be done. + fn promote(&self, cx: &LateContext<'_>, constructor_name: Symbol, value: u64) -> Option<(Symbol, u64)> { + let (best_unit, best_value) = self + .units + .iter() + .skip_while(|unit| unit.constructor_name != constructor_name) + .skip(1) + .try_fold( + (constructor_name, value), + |(current_unit, current_value), bigger_unit| { + if let Some(bigger_value) = current_value.div_exact(u64::from(bigger_unit.factor)) + && bigger_unit.stable_since.is_none_or(|v| self.msrv.meets(cx, v)) + { + ControlFlow::Continue((bigger_unit.constructor_name, bigger_value)) + } else { + // We have to break early, as we can't skip versions, as they are needed to + // correctly calculate the promoted value. + ControlFlow::Break((current_unit, current_value)) + } + }, + ) + .into_value(); + (best_unit != constructor_name).then_some((best_unit, best_value)) + } +} + +#[derive(Clone, Copy)] +struct Unit { + /// Name of the constructor on [`Duration`](std::time::Duration) to construct it from the given + /// unit, e.g. [`Duration::from_secs`](std::time::Duration::from_secs) + constructor_name: Symbol, + + /// The increase factor over the previous (smaller) unit + factor: u16, + + /// In what rustc version stable support for this constructor was added. + /// We do not need to track the version stable support in const contexts was added, as the const + /// stabilization was done in an ascending order of the time unites, so it's always valid to + /// promote a const constructor. + stable_since: Option, +} + +/// Time unit constructors available on stable. The order matters! +const UNITS: [Unit; 6] = [ + Unit { + constructor_name: sym::from_nanos, + // The value doesn't matter, as there is no previous unit + factor: 0, + stable_since: Some(msrvs::DURATION_FROM_NANOS_MICROS), + }, + Unit { + constructor_name: sym::from_micros, + factor: 1_000, + stable_since: Some(msrvs::DURATION_FROM_NANOS_MICROS), + }, + Unit { + constructor_name: sym::from_millis, + factor: 1_000, + stable_since: Some(msrvs::DURATION_FROM_MILLIS_SECS), + }, + Unit { + constructor_name: sym::from_secs, + factor: 1_000, + stable_since: Some(msrvs::DURATION_FROM_MILLIS_SECS), + }, + Unit { + constructor_name: sym::from_mins, + factor: 60, + stable_since: Some(msrvs::DURATION_FROM_MINUTES_HOURS), + }, + Unit { + constructor_name: sym::from_hours, + factor: 60, + stable_since: Some(msrvs::DURATION_FROM_MINUTES_HOURS), + }, +]; + +/// Time unit constructors behind the `duration_constructors` feature. The order matters! +const EXTENDED_UNITS: [Unit; 2] = [ + Unit { + constructor_name: sym::from_days, + factor: 24, + stable_since: None, + }, + Unit { + constructor_name: sym::from_weeks, + factor: 7, + stable_since: None, + }, +]; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a957afdb1910..ef2461f8b097 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,11 +1,13 @@ #![cfg_attr(bootstrap, feature(array_windows))] #![feature(box_patterns)] -#![feature(macro_metavar_expr_concat)] +#![feature(control_flow_into_value)] +#![feature(exact_div)] #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] #![feature(iter_intersperse)] #![feature(iter_partition_in_place)] +#![feature(macro_metavar_expr_concat)] #![feature(never_type)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] @@ -113,6 +115,7 @@ mod doc; mod double_parens; mod drop_forget_ref; mod duplicate_mod; +mod duration_suboptimal_units; mod else_if_without_else; mod empty_drop; mod empty_enums; @@ -857,6 +860,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(|_| Box::::default()), Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))), Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)), + Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 4a7fa3472cae..14cfb5a88283 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -23,6 +23,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { + 1,91,0 { DURATION_FROM_MINUTES_HOURS } 1,88,0 { LET_CHAINS } 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST } 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL } @@ -69,12 +70,12 @@ msrv_aliases! { 1,35,0 { OPTION_COPIED, RANGE_CONTAINS } 1,34,0 { TRY_FROM } 1,33,0 { UNDERSCORE_IMPORTS } - 1,32,0 { CONST_IS_POWER_OF_TWO } + 1,32,0 { CONST_IS_POWER_OF_TWO, CONST_DURATION_FROM_NANOS_MICROS_MILLIS_SECS } 1,31,0 { OPTION_REPLACE } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,29,0 { ITER_FLATTEN } 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF } - 1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND } + 1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND, DURATION_FROM_NANOS_MICROS } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS } 1,24,0 { IS_ASCII_DIGIT, PTR_NULL } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } @@ -82,6 +83,7 @@ msrv_aliases! { 1,16,0 { STR_REPEAT, RESULT_UNWRAP_OR_DEFAULT } 1,15,0 { MAYBE_BOUND_IN_WHERE } 1,13,0 { QUESTION_MARK_OPERATOR } + 1,3,0 { DURATION_FROM_MILLIS_SECS } } /// `#[clippy::msrv]` attributes are rarely used outside of Clippy's test suite, as a basic diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index a0d2e8673fe6..f11af159bc2e 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -140,6 +140,7 @@ generate! { disallowed_types, drain, dump, + duration_constructors, ends_with, enum_glob_use, enumerate, @@ -164,12 +165,20 @@ generate! { from_be_bytes, from_bytes_with_nul, from_bytes_with_nul_unchecked, + from_days, + from_hours, from_le_bytes, + from_micros, + from_millis, + from_mins, + from_nanos, from_ne_bytes, from_ptr, from_raw, from_raw_parts, + from_secs, from_str_radix, + from_weeks, fs, fuse, futures_util, diff --git a/tests/ui/duration_suboptimal_units.fixed b/tests/ui/duration_suboptimal_units.fixed new file mode 100644 index 000000000000..98c4b6e965ba --- /dev/null +++ b/tests/ui/duration_suboptimal_units.fixed @@ -0,0 +1,91 @@ +//@aux-build:proc_macros.rs +#![warn(clippy::duration_suboptimal_units)] + +use std::time::Duration; + +const SIXTY: u64 = 60; + +macro_rules! mac { + (slow_rythm) => { + 3600 + }; + (duration) => { + Duration::from_mins(5) + //~^ duration_suboptimal_units + }; + (arg => $e:expr) => { + Duration::from_secs($e) + }; +} + +fn main() { + let dur = Duration::from_secs(0); + let dur = Duration::from_secs(42); + let dur = Duration::from_hours(3); + + let dur = Duration::from_mins(1); + //~^ duration_suboptimal_units + let dur = Duration::from_mins(3); + //~^ duration_suboptimal_units + let dur = Duration::from_mins(10); + //~^ duration_suboptimal_units + let dur = Duration::from_hours(24); + //~^ duration_suboptimal_units + let dur = Duration::from_secs(5); + //~^ duration_suboptimal_units + let dur = Duration::from_hours(13); + //~^ duration_suboptimal_units + + // Constants are intentionally not resolved, as we don't want to recommend a literal value over + // using constants. + let dur = Duration::from_secs(SIXTY); + // Technically it would be nice to use Duration::from_mins(SIXTY) here, but that is a follow-up + let dur = Duration::from_secs(SIXTY * 60); + + const { + let dur = Duration::from_secs(0); + let dur = Duration::from_secs(5); + //~^ duration_suboptimal_units + let dur = Duration::from_mins(3); + //~^ duration_suboptimal_units + let dur = Duration::from_hours(24); + //~^ duration_suboptimal_units + + let dur = Duration::from_secs(SIXTY); + } + + // Qualified Durations must be kept + std::time::Duration::from_mins(1); + //~^ duration_suboptimal_units + + // We lint in normal macros + assert_eq!(Duration::from_hours(1), Duration::from_mins(6)); + //~^ duration_suboptimal_units + + // We lint in normal macros (marker is in macro itself) + let dur = mac!(duration); + + // We don't lint in macros if duration comes from outside + let dur = mac!(arg => 3600); + + // We don't lint in external macros + let dur = proc_macros::external! { Duration::from_secs(3_600) }; + + // We don't lint values coming from macros + let dur = Duration::from_secs(mac!(slow_rythm)); +} + +mod my_duration { + struct Duration {} + + impl Duration { + pub const fn from_secs(_secs: u64) -> Self { + Self {} + } + } + + fn test() { + // Only suggest the change for std::time::Duration, not for other Duration structs + let dur = Duration::from_secs(60); + } +} diff --git a/tests/ui/duration_suboptimal_units.rs b/tests/ui/duration_suboptimal_units.rs new file mode 100644 index 000000000000..c4f33a9f92e0 --- /dev/null +++ b/tests/ui/duration_suboptimal_units.rs @@ -0,0 +1,91 @@ +//@aux-build:proc_macros.rs +#![warn(clippy::duration_suboptimal_units)] + +use std::time::Duration; + +const SIXTY: u64 = 60; + +macro_rules! mac { + (slow_rythm) => { + 3600 + }; + (duration) => { + Duration::from_secs(300) + //~^ duration_suboptimal_units + }; + (arg => $e:expr) => { + Duration::from_secs($e) + }; +} + +fn main() { + let dur = Duration::from_secs(0); + let dur = Duration::from_secs(42); + let dur = Duration::from_hours(3); + + let dur = Duration::from_secs(60); + //~^ duration_suboptimal_units + let dur = Duration::from_secs(180); + //~^ duration_suboptimal_units + let dur = Duration::from_secs(10 * 60); + //~^ duration_suboptimal_units + let dur = Duration::from_mins(24 * 60); + //~^ duration_suboptimal_units + let dur = Duration::from_millis(5_000); + //~^ duration_suboptimal_units + let dur = Duration::from_nanos(13 * 60 * 60 * 1_000 * 1_000 * 1_000); + //~^ duration_suboptimal_units + + // Constants are intentionally not resolved, as we don't want to recommend a literal value over + // using constants. + let dur = Duration::from_secs(SIXTY); + // Technically it would be nice to use Duration::from_mins(SIXTY) here, but that is a follow-up + let dur = Duration::from_secs(SIXTY * 60); + + const { + let dur = Duration::from_secs(0); + let dur = Duration::from_millis(5_000); + //~^ duration_suboptimal_units + let dur = Duration::from_secs(180); + //~^ duration_suboptimal_units + let dur = Duration::from_mins(24 * 60); + //~^ duration_suboptimal_units + + let dur = Duration::from_secs(SIXTY); + } + + // Qualified Durations must be kept + std::time::Duration::from_secs(60); + //~^ duration_suboptimal_units + + // We lint in normal macros + assert_eq!(Duration::from_secs(3_600), Duration::from_mins(6)); + //~^ duration_suboptimal_units + + // We lint in normal macros (marker is in macro itself) + let dur = mac!(duration); + + // We don't lint in macros if duration comes from outside + let dur = mac!(arg => 3600); + + // We don't lint in external macros + let dur = proc_macros::external! { Duration::from_secs(3_600) }; + + // We don't lint values coming from macros + let dur = Duration::from_secs(mac!(slow_rythm)); +} + +mod my_duration { + struct Duration {} + + impl Duration { + pub const fn from_secs(_secs: u64) -> Self { + Self {} + } + } + + fn test() { + // Only suggest the change for std::time::Duration, not for other Duration structs + let dur = Duration::from_secs(60); + } +} diff --git a/tests/ui/duration_suboptimal_units.stderr b/tests/ui/duration_suboptimal_units.stderr new file mode 100644 index 000000000000..f129dbade8dc --- /dev/null +++ b/tests/ui/duration_suboptimal_units.stderr @@ -0,0 +1,152 @@ +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:26:15 + | +LL | let dur = Duration::from_secs(60); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::duration-suboptimal-units` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::duration_suboptimal_units)]` +help: try using from_mins + | +LL - let dur = Duration::from_secs(60); +LL + let dur = Duration::from_mins(1); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:28:15 + | +LL | let dur = Duration::from_secs(180); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_mins + | +LL - let dur = Duration::from_secs(180); +LL + let dur = Duration::from_mins(3); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:30:15 + | +LL | let dur = Duration::from_secs(10 * 60); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_mins + | +LL - let dur = Duration::from_secs(10 * 60); +LL + let dur = Duration::from_mins(10); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:32:15 + | +LL | let dur = Duration::from_mins(24 * 60); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_hours + | +LL - let dur = Duration::from_mins(24 * 60); +LL + let dur = Duration::from_hours(24); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:34:15 + | +LL | let dur = Duration::from_millis(5_000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_secs + | +LL - let dur = Duration::from_millis(5_000); +LL + let dur = Duration::from_secs(5); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:36:15 + | +LL | let dur = Duration::from_nanos(13 * 60 * 60 * 1_000 * 1_000 * 1_000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_hours + | +LL - let dur = Duration::from_nanos(13 * 60 * 60 * 1_000 * 1_000 * 1_000); +LL + let dur = Duration::from_hours(13); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:47:19 + | +LL | let dur = Duration::from_millis(5_000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_secs + | +LL - let dur = Duration::from_millis(5_000); +LL + let dur = Duration::from_secs(5); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:49:19 + | +LL | let dur = Duration::from_secs(180); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_mins + | +LL - let dur = Duration::from_secs(180); +LL + let dur = Duration::from_mins(3); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:51:19 + | +LL | let dur = Duration::from_mins(24 * 60); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_hours + | +LL - let dur = Duration::from_mins(24 * 60); +LL + let dur = Duration::from_hours(24); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:58:5 + | +LL | std::time::Duration::from_secs(60); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_mins + | +LL - std::time::Duration::from_secs(60); +LL + std::time::Duration::from_mins(1); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:62:16 + | +LL | assert_eq!(Duration::from_secs(3_600), Duration::from_mins(6)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_hours + | +LL - assert_eq!(Duration::from_secs(3_600), Duration::from_mins(6)); +LL + assert_eq!(Duration::from_hours(1), Duration::from_mins(6)); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units.rs:13:9 + | +LL | Duration::from_secs(300) + | ^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | let dur = mac!(duration); + | -------------- in this macro invocation + | + = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) +help: try using from_mins + | +LL - Duration::from_secs(300) +LL + Duration::from_mins(5) + | + +error: aborting due to 12 previous errors + diff --git a/tests/ui/duration_suboptimal_units_days_weeks.fixed b/tests/ui/duration_suboptimal_units_days_weeks.fixed new file mode 100644 index 000000000000..b0abcbb7bf03 --- /dev/null +++ b/tests/ui/duration_suboptimal_units_days_weeks.fixed @@ -0,0 +1,17 @@ +#![warn(clippy::duration_suboptimal_units)] +// The duration_constructors feature enables `Duration::from_days` and `Duration::from_weeks`, so we +// should suggest them +#![feature(duration_constructors)] + +use std::time::Duration; + +fn main() { + let dur = Duration::from_mins(1); + //~^ duration_suboptimal_units + + let dur = Duration::from_days(1); + //~^ duration_suboptimal_units + + let dur = Duration::from_weeks(13); + //~^ duration_suboptimal_units +} diff --git a/tests/ui/duration_suboptimal_units_days_weeks.rs b/tests/ui/duration_suboptimal_units_days_weeks.rs new file mode 100644 index 000000000000..663476905c0f --- /dev/null +++ b/tests/ui/duration_suboptimal_units_days_weeks.rs @@ -0,0 +1,17 @@ +#![warn(clippy::duration_suboptimal_units)] +// The duration_constructors feature enables `Duration::from_days` and `Duration::from_weeks`, so we +// should suggest them +#![feature(duration_constructors)] + +use std::time::Duration; + +fn main() { + let dur = Duration::from_secs(60); + //~^ duration_suboptimal_units + + let dur = Duration::from_hours(24); + //~^ duration_suboptimal_units + + let dur = Duration::from_nanos(13 * 7 * 24 * 60 * 60 * 1_000 * 1_000 * 1_000); + //~^ duration_suboptimal_units +} diff --git a/tests/ui/duration_suboptimal_units_days_weeks.stderr b/tests/ui/duration_suboptimal_units_days_weeks.stderr new file mode 100644 index 000000000000..98325358bfa6 --- /dev/null +++ b/tests/ui/duration_suboptimal_units_days_weeks.stderr @@ -0,0 +1,40 @@ +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units_days_weeks.rs:9:15 + | +LL | let dur = Duration::from_secs(60); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::duration-suboptimal-units` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::duration_suboptimal_units)]` +help: try using from_mins + | +LL - let dur = Duration::from_secs(60); +LL + let dur = Duration::from_mins(1); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units_days_weeks.rs:12:15 + | +LL | let dur = Duration::from_hours(24); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_days + | +LL - let dur = Duration::from_hours(24); +LL + let dur = Duration::from_days(1); + | + +error: constructing a `Duration` using a smaller unit when a larger unit would be more readable + --> tests/ui/duration_suboptimal_units_days_weeks.rs:15:15 + | +LL | let dur = Duration::from_nanos(13 * 7 * 24 * 60 * 60 * 1_000 * 1_000 * 1_000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try using from_weeks + | +LL - let dur = Duration::from_nanos(13 * 7 * 24 * 60 * 60 * 1_000 * 1_000 * 1_000); +LL + let dur = Duration::from_weeks(13); + | + +error: aborting due to 3 previous errors + From f73e504eea0a2cdd90fa1e27b8faa1ffd93ed370 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 9 Jan 2026 20:20:35 +0100 Subject: [PATCH 047/273] fix(useless_conversion): respect reduced applicability --- clippy_lints/src/useless_conversion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 423301edfe83..4ce132e9e3ab 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -365,7 +365,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { format!("useless conversion to the same type: `{b}`"), "consider removing `.into_iter()`", sugg, - Applicability::MachineApplicable, // snippet + applicability, ); } } From 3ec7666a2ac99ccfa9de268a89d855ceb380024c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 9 Jan 2026 20:43:26 +0100 Subject: [PATCH 048/273] clean-up --- clippy_lints/src/int_plus_one.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 67ce57de254d..540f9bb1e260 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -100,7 +100,7 @@ impl IntPlusOne { _ => None, } }, - // case where `... >= y - 1` or `... >= -1 + y` + // case where `... <= y - 1` or `... <= -1 + y` (BinOpKind::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => { match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) { // `-1 + y` @@ -156,11 +156,11 @@ impl IntPlusOne { } impl EarlyLintPass for IntPlusOne { - fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) { - if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::Binary(kind, lhs, rhs) = &expr.kind && let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) { - Self::emit_warning(cx, item, rec); + Self::emit_warning(cx, expr, rec); } } } From 0180ec4d5d9e47b1b36bf33d783ac72600c4abd4 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 9 Jan 2026 22:38:08 +0100 Subject: [PATCH 049/273] fix: negative literals are not literals --- clippy_lints/src/int_plus_one.rs | 67 ++++++++++++++++++-------------- tests/ui/int_plus_one.fixed | 16 ++++---- tests/ui/int_plus_one.rs | 16 ++++---- tests/ui/int_plus_one.stderr | 26 ++++++++++++- 4 files changed, 78 insertions(+), 47 deletions(-) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 540f9bb1e260..c25821751b3f 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::SpanRangeExt; -use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind}; -use rustc_ast::token; +use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp}; +use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -50,25 +50,36 @@ enum Side { } impl IntPlusOne { - #[expect(clippy::cast_sign_loss)] - fn check_lit(token_lit: token::Lit, target_value: i128) -> bool { - if let Ok(LitKind::Int(value, ..)) = LitKind::from_token_lit(token_lit) { - return value == (target_value as u128); + fn is_one(expr: &Expr) -> bool { + if let ExprKind::Lit(token_lit) = expr.kind + && let Ok(LitKind::Int(Pu128(1), ..)) = LitKind::from_token_lit(token_lit) + { + return true; } false } + fn is_neg_one(expr: &Expr) -> bool { + if let ExprKind::Unary(UnOp::Neg, expr) = &expr.kind + && Self::is_one(expr) + { + true + } else { + false + } + } + fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option { match (binop, &lhs.kind, &rhs.kind) { // case where `x - 1 >= ...` or `-1 + x >= ...` (BinOpKind::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => { - match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) { + match lhskind.node { // `-1 + x` - (BinOpKind::Add, ExprKind::Lit(lit), _) if Self::check_lit(*lit, -1) => { + BinOpKind::Add if Self::is_neg_one(lhslhs) => { Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) }, // `x - 1` - (BinOpKind::Sub, _, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => { + BinOpKind::Sub if Self::is_one(lhsrhs) => { Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) }, _ => None, @@ -76,39 +87,35 @@ impl IntPlusOne { }, // case where `... >= y + 1` or `... >= 1 + y` (BinOpKind::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => { - match (&rhslhs.kind, &rhsrhs.kind) { - // `y + 1` and `1 + y` - (ExprKind::Lit(lit), _) if Self::check_lit(*lit, 1) => { - Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) - }, - (_, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => { - Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) - }, - _ => None, + // `y + 1` and `1 + y` + if Self::is_one(rhslhs) { + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) + } else if Self::is_one(rhsrhs) { + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) + } else { + None } }, // case where `x + 1 <= ...` or `1 + x <= ...` (BinOpKind::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => { - match (&lhslhs.kind, &lhsrhs.kind) { - // `1 + x` and `x + 1` - (ExprKind::Lit(lit), _) if Self::check_lit(*lit, 1) => { - Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) - }, - (_, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => { - Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) - }, - _ => None, + // `1 + x` and `x + 1` + if Self::is_one(lhslhs) { + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) + } else if Self::is_one(lhsrhs) { + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) + } else { + None } }, // case where `... <= y - 1` or `... <= -1 + y` (BinOpKind::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => { - match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) { + match rhskind.node { // `-1 + y` - (BinOpKind::Add, ExprKind::Lit(lit), _) if Self::check_lit(*lit, -1) => { + BinOpKind::Add if Self::is_neg_one(rhslhs) => { Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) }, // `y - 1` - (BinOpKind::Sub, _, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => { + BinOpKind::Sub if Self::is_one(rhsrhs) => { Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) }, _ => None, diff --git a/tests/ui/int_plus_one.fixed b/tests/ui/int_plus_one.fixed index a1aba6bf7f64..cdd19515e9a7 100644 --- a/tests/ui/int_plus_one.fixed +++ b/tests/ui/int_plus_one.fixed @@ -4,15 +4,15 @@ fn main() { let x = 1i32; let y = 0i32; - let _ = x > y; - //~^ int_plus_one - let _ = y < x; - //~^ int_plus_one + let _ = x > y; //~ int_plus_one + let _ = x > y; //~ int_plus_one + let _ = y < x; //~ int_plus_one + let _ = y < x; //~ int_plus_one - let _ = x > y; - //~^ int_plus_one - let _ = y < x; - //~^ int_plus_one + let _ = x > y; //~ int_plus_one + let _ = x > y; //~ int_plus_one + let _ = y < x; //~ int_plus_one + let _ = y < x; //~ int_plus_one let _ = x > y; // should be ok let _ = y < x; // should be ok diff --git a/tests/ui/int_plus_one.rs b/tests/ui/int_plus_one.rs index f804fc9047de..8d7d2e8982d8 100644 --- a/tests/ui/int_plus_one.rs +++ b/tests/ui/int_plus_one.rs @@ -4,15 +4,15 @@ fn main() { let x = 1i32; let y = 0i32; - let _ = x >= y + 1; - //~^ int_plus_one - let _ = y + 1 <= x; - //~^ int_plus_one + let _ = x >= y + 1; //~ int_plus_one + let _ = x >= 1 + y; //~ int_plus_one + let _ = y + 1 <= x; //~ int_plus_one + let _ = 1 + y <= x; //~ int_plus_one - let _ = x - 1 >= y; - //~^ int_plus_one - let _ = y <= x - 1; - //~^ int_plus_one + let _ = x - 1 >= y; //~ int_plus_one + let _ = -1 + x >= y; //~ int_plus_one + let _ = y <= x - 1; //~ int_plus_one + let _ = y <= -1 + x; //~ int_plus_one let _ = x > y; // should be ok let _ = y < x; // should be ok diff --git a/tests/ui/int_plus_one.stderr b/tests/ui/int_plus_one.stderr index 052706028141..8bdff5680bdc 100644 --- a/tests/ui/int_plus_one.stderr +++ b/tests/ui/int_plus_one.stderr @@ -7,23 +7,47 @@ LL | let _ = x >= y + 1; = note: `-D clippy::int-plus-one` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::int_plus_one)]` +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:8:13 + | +LL | let _ = x >= 1 + y; + | ^^^^^^^^^^ help: change it to: `x > y` + error: unnecessary `>= y + 1` or `x - 1 >=` --> tests/ui/int_plus_one.rs:9:13 | LL | let _ = y + 1 <= x; | ^^^^^^^^^^ help: change it to: `y < x` +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:10:13 + | +LL | let _ = 1 + y <= x; + | ^^^^^^^^^^ help: change it to: `y < x` + error: unnecessary `>= y + 1` or `x - 1 >=` --> tests/ui/int_plus_one.rs:12:13 | LL | let _ = x - 1 >= y; | ^^^^^^^^^^ help: change it to: `x > y` +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:13:13 + | +LL | let _ = -1 + x >= y; + | ^^^^^^^^^^^ help: change it to: `x > y` + error: unnecessary `>= y + 1` or `x - 1 >=` --> tests/ui/int_plus_one.rs:14:13 | LL | let _ = y <= x - 1; | ^^^^^^^^^^ help: change it to: `y < x` -error: aborting due to 4 previous errors +error: unnecessary `>= y + 1` or `x - 1 >=` + --> tests/ui/int_plus_one.rs:15:13 + | +LL | let _ = y <= -1 + x; + | ^^^^^^^^^^^ help: change it to: `y < x` + +error: aborting due to 8 previous errors From df65d1a6a8509f9167078aab372f59ac7762af6c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 9 Jan 2026 20:39:42 +0100 Subject: [PATCH 050/273] introduce `LeOrGe` removes the need for a wildcard match --- clippy_lints/src/int_plus_one.rs | 63 ++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index c25821751b3f..8143dbad79e8 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -35,11 +35,11 @@ declare_clippy_lint! { declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]); // cases: -// BinOpKind::Ge +// LeOrGe::Ge // x >= y + 1 // x - 1 >= y // -// BinOpKind::Le +// LeOrGe::Le // x + 1 <= y // x <= y - 1 @@ -49,6 +49,23 @@ enum Side { Rhs, } +#[derive(Copy, Clone)] +enum LeOrGe { + Le, + Ge, +} + +impl TryFrom for LeOrGe { + type Error = (); + fn try_from(value: BinOpKind) -> Result { + match value { + BinOpKind::Le => Ok(Self::Le), + BinOpKind::Ge => Ok(Self::Ge), + _ => Err(()), + } + } +} + impl IntPlusOne { fn is_one(expr: &Expr) -> bool { if let ExprKind::Lit(token_lit) = expr.kind @@ -69,54 +86,54 @@ impl IntPlusOne { } } - fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option { - match (binop, &lhs.kind, &rhs.kind) { + fn check_binop(cx: &EarlyContext<'_>, le_or_ge: LeOrGe, lhs: &Expr, rhs: &Expr) -> Option { + match (le_or_ge, &lhs.kind, &rhs.kind) { // case where `x - 1 >= ...` or `-1 + x >= ...` - (BinOpKind::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => { + (LeOrGe::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => { match lhskind.node { // `-1 + x` BinOpKind::Add if Self::is_neg_one(lhslhs) => { - Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs, Side::Lhs) }, // `x - 1` BinOpKind::Sub if Self::is_one(lhsrhs) => { - Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs, Side::Lhs) }, _ => None, } }, // case where `... >= y + 1` or `... >= 1 + y` - (BinOpKind::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => { + (LeOrGe::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => { // `y + 1` and `1 + y` if Self::is_one(rhslhs) { - Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, rhsrhs, lhs, Side::Rhs) } else if Self::is_one(rhsrhs) { - Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, rhslhs, lhs, Side::Rhs) } else { None } }, // case where `x + 1 <= ...` or `1 + x <= ...` - (BinOpKind::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => { + (LeOrGe::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => { // `1 + x` and `x + 1` if Self::is_one(lhslhs) { - Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs, Side::Lhs) } else if Self::is_one(lhsrhs) { - Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs, Side::Lhs) } else { None } }, // case where `... <= y - 1` or `... <= -1 + y` - (BinOpKind::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => { + (LeOrGe::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => { match rhskind.node { // `-1 + y` BinOpKind::Add if Self::is_neg_one(rhslhs) => { - Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, rhsrhs, lhs, Side::Rhs) }, // `y - 1` BinOpKind::Sub if Self::is_one(rhsrhs) => { - Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, rhslhs, lhs, Side::Rhs) }, _ => None, } @@ -127,15 +144,14 @@ impl IntPlusOne { fn generate_recommendation( cx: &EarlyContext<'_>, - binop: BinOpKind, + le_or_ge: LeOrGe, node: &Expr, other_side: &Expr, side: Side, ) -> Option { - let binop_string = match binop { - BinOpKind::Ge => ">", - BinOpKind::Le => "<", - _ => return None, + let binop_string = match le_or_ge { + LeOrGe::Ge => ">", + LeOrGe::Le => "<", }; if let Some(snippet) = node.span.get_source_text(cx) && let Some(other_side_snippet) = other_side.span.get_source_text(cx) @@ -164,8 +180,9 @@ impl IntPlusOne { impl EarlyLintPass for IntPlusOne { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if let ExprKind::Binary(kind, lhs, rhs) = &expr.kind - && let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) + if let ExprKind::Binary(binop, lhs, rhs) = &expr.kind + && let Ok(le_or_ge) = LeOrGe::try_from(binop.node) + && let Some(rec) = Self::check_binop(cx, le_or_ge, lhs, rhs) { Self::emit_warning(cx, expr, rec); } From d554092c27f0ad5cf8dc013b4fbcbd7853c40c0c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 9 Jan 2026 21:01:55 +0100 Subject: [PATCH 051/273] get rid of `Side` --- clippy_lints/src/int_plus_one.rs | 33 +++++++++----------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 8143dbad79e8..97b7afb61d86 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -43,12 +43,6 @@ declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]); // x + 1 <= y // x <= y - 1 -#[derive(Copy, Clone)] -enum Side { - Lhs, - Rhs, -} - #[derive(Copy, Clone)] enum LeOrGe { Le, @@ -93,12 +87,10 @@ impl IntPlusOne { match lhskind.node { // `-1 + x` BinOpKind::Add if Self::is_neg_one(lhslhs) => { - Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs) }, // `x - 1` - BinOpKind::Sub if Self::is_one(lhsrhs) => { - Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs, Side::Lhs) - }, + BinOpKind::Sub if Self::is_one(lhsrhs) => Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs), _ => None, } }, @@ -106,9 +98,9 @@ impl IntPlusOne { (LeOrGe::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => { // `y + 1` and `1 + y` if Self::is_one(rhslhs) { - Self::generate_recommendation(cx, le_or_ge, rhsrhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, lhs, rhsrhs) } else if Self::is_one(rhsrhs) { - Self::generate_recommendation(cx, le_or_ge, rhslhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, lhs, rhslhs) } else { None } @@ -117,9 +109,9 @@ impl IntPlusOne { (LeOrGe::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => { // `1 + x` and `x + 1` if Self::is_one(lhslhs) { - Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs) } else if Self::is_one(lhsrhs) { - Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs, Side::Lhs) + Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs) } else { None } @@ -129,12 +121,10 @@ impl IntPlusOne { match rhskind.node { // `-1 + y` BinOpKind::Add if Self::is_neg_one(rhslhs) => { - Self::generate_recommendation(cx, le_or_ge, rhsrhs, lhs, Side::Rhs) + Self::generate_recommendation(cx, le_or_ge, lhs, rhsrhs) }, // `y - 1` - BinOpKind::Sub if Self::is_one(rhsrhs) => { - Self::generate_recommendation(cx, le_or_ge, rhslhs, lhs, Side::Rhs) - }, + BinOpKind::Sub if Self::is_one(rhsrhs) => Self::generate_recommendation(cx, le_or_ge, lhs, rhslhs), _ => None, } }, @@ -147,7 +137,6 @@ impl IntPlusOne { le_or_ge: LeOrGe, node: &Expr, other_side: &Expr, - side: Side, ) -> Option { let binop_string = match le_or_ge { LeOrGe::Ge => ">", @@ -156,11 +145,7 @@ impl IntPlusOne { if let Some(snippet) = node.span.get_source_text(cx) && let Some(other_side_snippet) = other_side.span.get_source_text(cx) { - let rec = match side { - Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")), - Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")), - }; - return rec; + return Some(format!("{snippet} {binop_string} {other_side_snippet}")); } None } From ccaa2fa9841447092bfe96111629747a27fa419a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 9 Jan 2026 20:30:20 +0100 Subject: [PATCH 052/273] fix: respect reduced applicability By building the whole suggestion in `emit_suggestion`, we avoid the need to thread `Applicability` through `check_binop` into `generate_suggestion` --- clippy_lints/src/int_plus_one.rs | 69 +++++++++++++------------------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 97b7afb61d86..b82ca0205a9f 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -1,5 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg; use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp}; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; @@ -80,17 +80,15 @@ impl IntPlusOne { } } - fn check_binop(cx: &EarlyContext<'_>, le_or_ge: LeOrGe, lhs: &Expr, rhs: &Expr) -> Option { + fn check_binop<'tcx>(le_or_ge: LeOrGe, lhs: &'tcx Expr, rhs: &'tcx Expr) -> Option<(&'tcx Expr, &'tcx Expr)> { match (le_or_ge, &lhs.kind, &rhs.kind) { // case where `x - 1 >= ...` or `-1 + x >= ...` (LeOrGe::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => { match lhskind.node { // `-1 + x` - BinOpKind::Add if Self::is_neg_one(lhslhs) => { - Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs) - }, + BinOpKind::Add if Self::is_neg_one(lhslhs) => Some((lhsrhs, rhs)), // `x - 1` - BinOpKind::Sub if Self::is_one(lhsrhs) => Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs), + BinOpKind::Sub if Self::is_one(lhsrhs) => Some((lhslhs, rhs)), _ => None, } }, @@ -98,9 +96,9 @@ impl IntPlusOne { (LeOrGe::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => { // `y + 1` and `1 + y` if Self::is_one(rhslhs) { - Self::generate_recommendation(cx, le_or_ge, lhs, rhsrhs) + Some((lhs, rhsrhs)) } else if Self::is_one(rhsrhs) { - Self::generate_recommendation(cx, le_or_ge, lhs, rhslhs) + Some((lhs, rhslhs)) } else { None } @@ -109,9 +107,9 @@ impl IntPlusOne { (LeOrGe::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => { // `1 + x` and `x + 1` if Self::is_one(lhslhs) { - Self::generate_recommendation(cx, le_or_ge, lhsrhs, rhs) + Some((lhsrhs, rhs)) } else if Self::is_one(lhsrhs) { - Self::generate_recommendation(cx, le_or_ge, lhslhs, rhs) + Some((lhslhs, rhs)) } else { None } @@ -120,11 +118,9 @@ impl IntPlusOne { (LeOrGe::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => { match rhskind.node { // `-1 + y` - BinOpKind::Add if Self::is_neg_one(rhslhs) => { - Self::generate_recommendation(cx, le_or_ge, lhs, rhsrhs) - }, + BinOpKind::Add if Self::is_neg_one(rhslhs) => Some((lhs, rhsrhs)), // `y - 1` - BinOpKind::Sub if Self::is_one(rhsrhs) => Self::generate_recommendation(cx, le_or_ge, lhs, rhslhs), + BinOpKind::Sub if Self::is_one(rhsrhs) => Some((lhs, rhslhs)), _ => None, } }, @@ -132,33 +128,24 @@ impl IntPlusOne { } } - fn generate_recommendation( - cx: &EarlyContext<'_>, - le_or_ge: LeOrGe, - node: &Expr, - other_side: &Expr, - ) -> Option { - let binop_string = match le_or_ge { - LeOrGe::Ge => ">", - LeOrGe::Le => "<", - }; - if let Some(snippet) = node.span.get_source_text(cx) - && let Some(other_side_snippet) = other_side.span.get_source_text(cx) - { - return Some(format!("{snippet} {binop_string} {other_side_snippet}")); - } - None - } - - fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) { - span_lint_and_sugg( + fn emit_warning(cx: &EarlyContext<'_>, expr: &Expr, new_lhs: &Expr, le_or_ge: LeOrGe, new_rhs: &Expr) { + span_lint_and_then( cx, INT_PLUS_ONE, - block.span, + expr.span, "unnecessary `>= y + 1` or `x - 1 >=`", - "change it to", - recommendation, - Applicability::MachineApplicable, // snippet + |diag| { + let mut app = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); + let new_lhs = sugg::Sugg::ast(cx, new_lhs, "_", ctxt, &mut app); + let new_rhs = sugg::Sugg::ast(cx, new_rhs, "_", ctxt, &mut app); + let new_binop = match le_or_ge { + LeOrGe::Ge => BinOpKind::Gt, + LeOrGe::Le => BinOpKind::Lt, + }; + let rec = sugg::make_binop(new_binop, &new_lhs, &new_rhs); + diag.span_suggestion(expr.span, "change it to", rec, app); + }, ); } } @@ -167,9 +154,9 @@ impl EarlyLintPass for IntPlusOne { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::Binary(binop, lhs, rhs) = &expr.kind && let Ok(le_or_ge) = LeOrGe::try_from(binop.node) - && let Some(rec) = Self::check_binop(cx, le_or_ge, lhs, rhs) + && let Some((new_lhs, new_rhs)) = Self::check_binop(le_or_ge, lhs, rhs) { - Self::emit_warning(cx, expr, rec); + Self::emit_warning(cx, expr, new_lhs, le_or_ge, new_rhs); } } } From ea591b55f43483bacb2e8b2b1cb9f6c7aa999d7e Mon Sep 17 00:00:00 2001 From: Andre Bogus Date: Fri, 9 Jan 2026 23:15:06 +0100 Subject: [PATCH 053/273] fix `clippy_utils::std_or_core(_)` --- clippy_utils/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 2a620e917228..33f5f5c8873d 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2031,12 +2031,12 @@ pub fn is_expr_temporary_value(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { - if !is_no_std_crate(cx) { - Some("std") - } else if !is_no_core_crate(cx) { + if is_no_core_crate(cx) { + None + } else if is_no_std_crate(cx) { Some("core") } else { - None + Some("std") } } From 3f51a315e0668ff1c72459a08dfc42780032918c Mon Sep 17 00:00:00 2001 From: Paul Mabileau Date: Fri, 9 Jan 2026 21:20:39 +0100 Subject: [PATCH 054/273] Fix(lib/win/net): Remove hostname support under Win7 `GetHostNameW` is not available under Windows 7, leading to dynamic linking failures upon program executions. For now, as it is still unstable, this therefore appropriately cfg-gates the feature in order to mark the Win7 as unsupported with regards to this particular feature. Porting the functionality for Windows 7 would require changing the underlying system call and so more work for the immediate need. Signed-off-by: Paul Mabileau --- library/std/src/net/hostname.rs | 8 ++++---- library/std/src/sys/net/hostname/mod.rs | 3 ++- library/std/src/sys/pal/windows/c.rs | 4 ++++ library/std/src/sys/pal/windows/c/bindings.txt | 1 - library/std/src/sys/pal/windows/c/windows_sys.rs | 1 - 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/library/std/src/net/hostname.rs b/library/std/src/net/hostname.rs index b1010cec6005..4042496d534f 100644 --- a/library/std/src/net/hostname.rs +++ b/library/std/src/net/hostname.rs @@ -8,10 +8,10 @@ use crate::ffi::OsString; /// /// # Underlying system calls /// -/// | Platform | System call | -/// |----------|---------------------------------------------------------------------------------------------------------| -/// | UNIX | [`gethostname`](https://www.man7.org/linux/man-pages/man2/gethostname.2.html) | -/// | Windows | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) | +/// | Platform | System call | +/// |--------------|---------------------------------------------------------------------------------------------------------| +/// | UNIX | [`gethostname`](https://www.man7.org/linux/man-pages/man2/gethostname.2.html) | +/// | Windows (8+) | [`GetHostNameW`](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew) | /// /// Note that platform-specific behavior [may change in the future][changes]. /// diff --git a/library/std/src/sys/net/hostname/mod.rs b/library/std/src/sys/net/hostname/mod.rs index 8ffe4894d718..65fcd6bfb00d 100644 --- a/library/std/src/sys/net/hostname/mod.rs +++ b/library/std/src/sys/net/hostname/mod.rs @@ -3,7 +3,8 @@ cfg_select! { mod unix; pub use unix::hostname; } - target_os = "windows" => { + // `GetHostNameW` is only available starting with Windows 8. + all(target_os = "windows", not(target_vendor = "win7")) => { mod windows; pub use windows::hostname; } diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs index 25c1a82cc426..0f54f80d72ee 100644 --- a/library/std/src/sys/pal/windows/c.rs +++ b/library/std/src/sys/pal/windows/c.rs @@ -237,3 +237,7 @@ cfg_select! { } _ => {} } + +// Only available starting with Windows 8. +#[cfg(not(target_vendor = "win7"))] +windows_targets::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32); diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index 9009aa09f48e..12babcb84ccf 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2170,7 +2170,6 @@ GetFileType GETFINALPATHNAMEBYHANDLE_FLAGS GetFinalPathNameByHandleW GetFullPathNameW -GetHostNameW GetLastError GetModuleFileNameW GetModuleHandleA diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 98f277b33780..edc9d2d11f7c 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -49,7 +49,6 @@ windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE); windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32); windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32); -windows_targets::link!("ws2_32.dll" "system" fn GetHostNameW(name : PWSTR, namelen : i32) -> i32); windows_targets::link!("kernel32.dll" "system" fn GetLastError() -> WIN32_ERROR); windows_targets::link!("kernel32.dll" "system" fn GetModuleFileNameW(hmodule : HMODULE, lpfilename : PWSTR, nsize : u32) -> u32); windows_targets::link!("kernel32.dll" "system" fn GetModuleHandleA(lpmodulename : PCSTR) -> HMODULE); From f4a6e4f0d94670e162370c2a495b0f1a8bb43262 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Thu, 8 Jan 2026 12:19:43 +0900 Subject: [PATCH 055/273] Fix clippy --- clippy_lints/src/utils/author.rs | 1 + clippy_utils/src/consts.rs | 1 + clippy_utils/src/hir_utils.rs | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index acea701b2e83..8243077cab89 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -321,6 +321,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { }, ConstArgKind::Struct(..) => chain!(self, "let ConstArgKind::Struct(..) = {const_arg}.kind"), ConstArgKind::TupleCall(..) => chain!(self, "let ConstArgKind::TupleCall(..) = {const_arg}.kind"), + ConstArgKind::Array(..) => chain!(self, "let ConstArgKind::Array(..) = {const_arg}.kind"), ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"), ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"), ConstArgKind::Tup(..) => chain!(self, "let ConstArgKind::Tup(..) = {const_arg}.kind"), diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 538f1fd2628c..e2ada37d53b3 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1144,6 +1144,7 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx | ConstArgKind::Tup(..) | ConstArgKind::Literal(..) | ConstArgKind::TupleCall(..) + | ConstArgKind::Array(..) | ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 0bb641568879..c7bb3a064a09 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -687,6 +687,11 @@ impl HirEqInterExpr<'_, '_, '_> { .all(|(arg_a, arg_b)| self.eq_const_arg(arg_a, arg_b)) }, (ConstArgKind::Literal(kind_l), ConstArgKind::Literal(kind_r)) => kind_l == kind_r, + (ConstArgKind::Array(l_arr), ConstArgKind::Array(r_arr)) => { + l_arr.elems.len() == r_arr.elems.len() + && l_arr.elems.iter().zip(r_arr.elems.iter()) + .all(|(l_elem, r_elem)| self.eq_const_arg(l_elem, r_elem)) + } // Use explicit match for now since ConstArg is undergoing flux. ( ConstArgKind::Path(..) @@ -696,6 +701,7 @@ impl HirEqInterExpr<'_, '_, '_> { | ConstArgKind::Infer(..) | ConstArgKind::Struct(..) | ConstArgKind::Literal(..) + | ConstArgKind::Array(..) | ConstArgKind::Error(..), _, ) => false, @@ -1575,6 +1581,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_const_arg(arg); } }, + ConstArgKind::Array(array_expr) => { + for elem in array_expr.elems { + self.hash_const_arg(elem); + } + }, ConstArgKind::Infer(..) | ConstArgKind::Error(..) => {}, ConstArgKind::Literal(lit) => lit.hash(&mut self.s), } From c87bb01b9e8bc2419f95bba7f06f1ccee0cabca5 Mon Sep 17 00:00:00 2001 From: Aliaksei Semianiuk Date: Sat, 10 Jan 2026 15:34:41 +0500 Subject: [PATCH 056/273] Changelog for Clippy 1.93 --- CHANGELOG.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cda8003b05c..afefd436aada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,66 @@ document. ## Unreleased / Beta / In Rust Nightly -[d9fb15c...master](https://github.com/rust-lang/rust-clippy/compare/d9fb15c...master) +[92b4b68...master](https://github.com/rust-lang/rust-clippy/compare/92b4b68...master) + +## Rust 1.93 + +Current stable, released 2026-01-22 + +[View all 96 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-10-17T15%3A48%3A11Z..2025-11-28T19%3A22%3A54Z+base%3Amaster) + +### New Lints + +* Added [`doc_paragraphs_missing_punctuation`] to `restriction` + [#15758](https://github.com/rust-lang/rust-clippy/pull/15758) + +### Moves and Deprecations + +* Renamed [`needless_if`] to [`needless_ifs`] + [#15961](https://github.com/rust-lang/rust-clippy/pull/15961) +* Renamed [`empty_enum`] to [`empty_enums`] + [#15912](https://github.com/rust-lang/rust-clippy/pull/15912) + +### Enhancements + +* [`result_large_err`] added `large_error_ignored` configuration + [#15697](https://github.com/rust-lang/rust-clippy/pull/15697) +* [`explicit_deref_methods`] don't lint in `impl Deref(Mut)` + [#16113](https://github.com/rust-lang/rust-clippy/pull/16113) +* [`missing_docs_in_private_items`] don't lint items in bodies and automatically derived impls; + better detect when things are accessible from the crate root; lint unnameable items which are + accessible outside the crate + [#14741](https://github.com/rust-lang/rust-clippy/pull/14741) +* [`unnecessary_unwrap`] and [`panicking_unwrap`] lint field accesses + [#15949](https://github.com/rust-lang/rust-clippy/pull/15949) +* [`ok_expect`] add autofix + [#15867](https://github.com/rust-lang/rust-clippy/pull/15867) +* [`let_and_return`] disallow _any_ text between let and return + [#16006](https://github.com/rust-lang/rust-clippy/pull/16006) +* [`needless_collect`] extend to lint more cases + [#14361](https://github.com/rust-lang/rust-clippy/pull/14361) +* [`needless_doctest_main`] and [`test_attr_in_doctest`] now handle whitespace in language tags + [#15967](https://github.com/rust-lang/rust-clippy/pull/15967) +* [`search_is_some`] now fixes code spanning multiple lines + [#15902](https://github.com/rust-lang/rust-clippy/pull/15902) +* [`unnecessary_find_map`] and [`unnecessary_filter_map`] make diagnostic spans more precise + [#15929](https://github.com/rust-lang/rust-clippy/pull/15929) +* [`precedence`] warn about ambiguity when a closure is used as a method call receiver + [#14421](https://github.com/rust-lang/rust-clippy/pull/14421) +* [`match_as_ref`] suggest `as_ref` when the reference needs to be cast; improve diagnostics + [#15934](https://github.com/rust-lang/rust-clippy/pull/15934) + [#15928](https://github.com/rust-lang/rust-clippy/pull/15928) + +### False Positive Fixes + +* [`single_range_in_vec_init`] fix FP for explicit `Range` + [#16043](https://github.com/rust-lang/rust-clippy/pull/16043) +* [`mod_module_files`] fix false positive for integration tests in workspace crates + [#16048](https://github.com/rust-lang/rust-clippy/pull/16048) +* [`replace_box`] fix FP when the box is moved + [#15984](https://github.com/rust-lang/rust-clippy/pull/15984) +* [`len_zero`] fix FP on unstable methods + [#15894](https://github.com/rust-lang/rust-clippy/pull/15894) ## Rust 1.92 From 9eb4d7c56ebf768576183e5c5fd3e8159520dd22 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 10 Jan 2026 13:58:47 +0100 Subject: [PATCH 057/273] remove `no-rustfix` the suggestions are no longer overlapping?.. --- tests/ui/suspicious_to_owned.1.fixed | 73 ++++++++++++++++++++++++++++ tests/ui/suspicious_to_owned.2.fixed | 73 ++++++++++++++++++++++++++++ tests/ui/suspicious_to_owned.rs | 1 - tests/ui/suspicious_to_owned.stderr | 12 ++--- 4 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 tests/ui/suspicious_to_owned.1.fixed create mode 100644 tests/ui/suspicious_to_owned.2.fixed diff --git a/tests/ui/suspicious_to_owned.1.fixed b/tests/ui/suspicious_to_owned.1.fixed new file mode 100644 index 000000000000..6fd536a38ed1 --- /dev/null +++ b/tests/ui/suspicious_to_owned.1.fixed @@ -0,0 +1,73 @@ +#![warn(clippy::suspicious_to_owned)] +#![warn(clippy::implicit_clone)] +#![allow(clippy::redundant_clone)] +use std::borrow::Cow; +use std::ffi::{CStr, c_char}; + +fn main() { + let moo = "Moooo"; + let c_moo = b"Moooo\0"; + let c_moo_ptr = c_moo.as_ptr() as *const c_char; + let moos = ['M', 'o', 'o']; + let moos_vec = moos.to_vec(); + + // we expect this to be linted + let cow = Cow::Borrowed(moo); + let _ = cow.into_owned(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = Cow::Borrowed(moo); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(moo); + let _ = cow.clone(); + + // we expect this to be linted + let cow = Cow::Borrowed(&moos); + let _ = cow.into_owned(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = Cow::Borrowed(&moos); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos); + let _ = cow.clone(); + + // we expect this to be linted + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.into_owned(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.clone(); + + // we expect this to be linted + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.into_owned(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.clone(); + + // we expect no lints for these + let _ = moo.to_owned(); + let _ = c_moo.to_owned(); + let _ = moos.to_owned(); + + // we expect implicit_clone lints for these + let _ = String::from(moo).clone(); + //~^ implicit_clone + + let _ = moos_vec.clone(); + //~^ implicit_clone +} diff --git a/tests/ui/suspicious_to_owned.2.fixed b/tests/ui/suspicious_to_owned.2.fixed new file mode 100644 index 000000000000..841adf8ea274 --- /dev/null +++ b/tests/ui/suspicious_to_owned.2.fixed @@ -0,0 +1,73 @@ +#![warn(clippy::suspicious_to_owned)] +#![warn(clippy::implicit_clone)] +#![allow(clippy::redundant_clone)] +use std::borrow::Cow; +use std::ffi::{CStr, c_char}; + +fn main() { + let moo = "Moooo"; + let c_moo = b"Moooo\0"; + let c_moo_ptr = c_moo.as_ptr() as *const c_char; + let moos = ['M', 'o', 'o']; + let moos_vec = moos.to_vec(); + + // we expect this to be linted + let cow = Cow::Borrowed(moo); + let _ = cow.clone(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = Cow::Borrowed(moo); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(moo); + let _ = cow.clone(); + + // we expect this to be linted + let cow = Cow::Borrowed(&moos); + let _ = cow.clone(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = Cow::Borrowed(&moos); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos); + let _ = cow.clone(); + + // we expect this to be linted + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.clone(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = Cow::Borrowed(&moos_vec); + let _ = cow.clone(); + + // we expect this to be linted + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.clone(); + //~^ suspicious_to_owned + + // we expect no lints for this + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.into_owned(); + // we expect no lints for this + let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy(); + let _ = cow.clone(); + + // we expect no lints for these + let _ = moo.to_owned(); + let _ = c_moo.to_owned(); + let _ = moos.to_owned(); + + // we expect implicit_clone lints for these + let _ = String::from(moo).clone(); + //~^ implicit_clone + + let _ = moos_vec.clone(); + //~^ implicit_clone +} diff --git a/tests/ui/suspicious_to_owned.rs b/tests/ui/suspicious_to_owned.rs index 2eec05ccaf4c..f59b3fd6ed0c 100644 --- a/tests/ui/suspicious_to_owned.rs +++ b/tests/ui/suspicious_to_owned.rs @@ -1,4 +1,3 @@ -//@no-rustfix: overlapping suggestions #![warn(clippy::suspicious_to_owned)] #![warn(clippy::implicit_clone)] #![allow(clippy::redundant_clone)] diff --git a/tests/ui/suspicious_to_owned.stderr b/tests/ui/suspicious_to_owned.stderr index f90bea5fb8ff..1e631d4d4a14 100644 --- a/tests/ui/suspicious_to_owned.stderr +++ b/tests/ui/suspicious_to_owned.stderr @@ -1,5 +1,5 @@ error: this `to_owned` call clones the Cow<'_, str> itself and does not cause the Cow<'_, str> contents to become owned - --> tests/ui/suspicious_to_owned.rs:17:13 + --> tests/ui/suspicious_to_owned.rs:16:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL + let _ = cow.clone(); | error: this `to_owned` call clones the Cow<'_, [char; 3]> itself and does not cause the Cow<'_, [char; 3]> contents to become owned - --> tests/ui/suspicious_to_owned.rs:29:13 + --> tests/ui/suspicious_to_owned.rs:28:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ @@ -33,7 +33,7 @@ LL + let _ = cow.clone(); | error: this `to_owned` call clones the Cow<'_, Vec> itself and does not cause the Cow<'_, Vec> contents to become owned - --> tests/ui/suspicious_to_owned.rs:41:13 + --> tests/ui/suspicious_to_owned.rs:40:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + let _ = cow.clone(); | error: this `to_owned` call clones the Cow<'_, str> itself and does not cause the Cow<'_, str> contents to become owned - --> tests/ui/suspicious_to_owned.rs:53:13 + --> tests/ui/suspicious_to_owned.rs:52:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL + let _ = cow.clone(); | error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type - --> tests/ui/suspicious_to_owned.rs:69:13 + --> tests/ui/suspicious_to_owned.rs:68:13 | LL | let _ = String::from(moo).to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::from(moo).clone()` @@ -74,7 +74,7 @@ LL | let _ = String::from(moo).to_owned(); = help: to override `-D warnings` add `#[allow(clippy::implicit_clone)]` error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type - --> tests/ui/suspicious_to_owned.rs:72:13 + --> tests/ui/suspicious_to_owned.rs:71:13 | LL | let _ = moos_vec.to_owned(); | ^^^^^^^^^^^^^^^^^^^ help: consider using: `moos_vec.clone()` From f228056c644969488958f01cb88a89f0a93f642d Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 10 Jan 2026 13:51:44 +0100 Subject: [PATCH 058/273] improve lint messages - only mention the type once - put types in backticks - only highlight the method name in the suggestion - removes the need for a snippet - makes for finer diffs (seen through `cargo dev lint`) --- clippy_lints/src/methods/mod.rs | 2 +- .../src/methods/suspicious_to_owned.rs | 23 +++++++----------- tests/ui/suspicious_to_owned.stderr | 24 +++++++++---------- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 248a147cfd77..d483496549fd 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5576,7 +5576,7 @@ impl Methods { unnecessary_fallible_conversions::check_method(cx, expr); }, (sym::to_owned, []) => { - if !suspicious_to_owned::check(cx, expr, recv) { + if !suspicious_to_owned::check(cx, expr, span) { implicit_clone::check(cx, name, expr, recv); } }, diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints/src/methods/suspicious_to_owned.rs index bcd1f11931fc..e9a5104eb3b4 100644 --- a/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/clippy_lints/src/methods/suspicious_to_owned.rs @@ -1,15 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet_with_context; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_span::sym; +use rustc_span::{Span, sym}; use super::SUSPICIOUS_TO_OWNED; -pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool { +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span) -> bool { if cx .typeck_results() .type_dependent_def_id(expr.hir_id) @@ -18,28 +17,22 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) - && let input_type = cx.typeck_results().expr_ty(expr) && input_type.is_diag_item(cx, sym::Cow) { - let mut app = Applicability::MaybeIncorrect; - let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; + let app = Applicability::MaybeIncorrect; span_lint_and_then( cx, SUSPICIOUS_TO_OWNED, expr.span, with_forced_trimmed_paths!(format!( - "this `to_owned` call clones the {input_type} itself and does not cause the {input_type} contents to become owned" + "this `to_owned` call clones the `{input_type}` itself and does not cause its contents to become owned" )), |diag| { diag.span_suggestion( - expr.span, - "depending on intent, either make the Cow an Owned variant", - format!("{recv_snip}.into_owned()"), - app, - ); - diag.span_suggestion( - expr.span, - "or clone the Cow itself", - format!("{recv_snip}.clone()"), + method_span, + "depending on intent, either make the `Cow` an `Owned` variant", + "into_owned".to_string(), app, ); + diag.span_suggestion(method_span, "or clone the `Cow` itself", "clone".to_string(), app); }, ); return true; diff --git a/tests/ui/suspicious_to_owned.stderr b/tests/ui/suspicious_to_owned.stderr index 1e631d4d4a14..5fda1ed1cc9c 100644 --- a/tests/ui/suspicious_to_owned.stderr +++ b/tests/ui/suspicious_to_owned.stderr @@ -1,4 +1,4 @@ -error: this `to_owned` call clones the Cow<'_, str> itself and does not cause the Cow<'_, str> contents to become owned +error: this `to_owned` call clones the `Cow<'_, str>` itself and does not cause its contents to become owned --> tests/ui/suspicious_to_owned.rs:16:13 | LL | let _ = cow.to_owned(); @@ -6,59 +6,59 @@ LL | let _ = cow.to_owned(); | = note: `-D clippy::suspicious-to-owned` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::suspicious_to_owned)]` -help: depending on intent, either make the Cow an Owned variant +help: depending on intent, either make the `Cow` an `Owned` variant | LL | let _ = cow.into_owned(); | ++ -help: or clone the Cow itself +help: or clone the `Cow` itself | LL - let _ = cow.to_owned(); LL + let _ = cow.clone(); | -error: this `to_owned` call clones the Cow<'_, [char; 3]> itself and does not cause the Cow<'_, [char; 3]> contents to become owned +error: this `to_owned` call clones the `Cow<'_, [char; 3]>` itself and does not cause its contents to become owned --> tests/ui/suspicious_to_owned.rs:28:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ | -help: depending on intent, either make the Cow an Owned variant +help: depending on intent, either make the `Cow` an `Owned` variant | LL | let _ = cow.into_owned(); | ++ -help: or clone the Cow itself +help: or clone the `Cow` itself | LL - let _ = cow.to_owned(); LL + let _ = cow.clone(); | -error: this `to_owned` call clones the Cow<'_, Vec> itself and does not cause the Cow<'_, Vec> contents to become owned +error: this `to_owned` call clones the `Cow<'_, Vec>` itself and does not cause its contents to become owned --> tests/ui/suspicious_to_owned.rs:40:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ | -help: depending on intent, either make the Cow an Owned variant +help: depending on intent, either make the `Cow` an `Owned` variant | LL | let _ = cow.into_owned(); | ++ -help: or clone the Cow itself +help: or clone the `Cow` itself | LL - let _ = cow.to_owned(); LL + let _ = cow.clone(); | -error: this `to_owned` call clones the Cow<'_, str> itself and does not cause the Cow<'_, str> contents to become owned +error: this `to_owned` call clones the `Cow<'_, str>` itself and does not cause its contents to become owned --> tests/ui/suspicious_to_owned.rs:52:13 | LL | let _ = cow.to_owned(); | ^^^^^^^^^^^^^^ | -help: depending on intent, either make the Cow an Owned variant +help: depending on intent, either make the `Cow` an `Owned` variant | LL | let _ = cow.into_owned(); | ++ -help: or clone the Cow itself +help: or clone the `Cow` itself | LL - let _ = cow.to_owned(); LL + let _ = cow.clone(); From e4dd9475234017adeda572d02320876f98e26775 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 10 Jan 2026 19:57:28 +0100 Subject: [PATCH 059/273] introduce `Self::as_x_{plus,minus}_one` reduces duplication --- clippy_lints/src/int_plus_one.rs | 91 +++++++++++++++----------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index b82ca0205a9f..f8184b30f400 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -63,7 +63,7 @@ impl TryFrom for LeOrGe { impl IntPlusOne { fn is_one(expr: &Expr) -> bool { if let ExprKind::Lit(token_lit) = expr.kind - && let Ok(LitKind::Int(Pu128(1), ..)) = LitKind::from_token_lit(token_lit) + && matches!(LitKind::from_token_lit(token_lit), Ok(LitKind::Int(Pu128(1), ..))) { return true; } @@ -71,60 +71,57 @@ impl IntPlusOne { } fn is_neg_one(expr: &Expr) -> bool { - if let ExprKind::Unary(UnOp::Neg, expr) = &expr.kind - && Self::is_one(expr) - { - true + if let ExprKind::Unary(UnOp::Neg, expr) = &expr.kind { + Self::is_one(expr) } else { false } } + /// Checks whether `expr` is `x + 1` or `1 + x`, and if so, returns `x` + fn as_x_plus_one(expr: &Expr) -> Option<&Expr> { + if let ExprKind::Binary(op, lhs, rhs) = &expr.kind + && op.node == BinOpKind::Add + { + if Self::is_one(rhs) { + // x + 1 + return Some(lhs); + } else if Self::is_one(lhs) { + // 1 + x + return Some(rhs); + } + } + None + } + + /// Checks whether `expr` is `x - 1` or `-1 + x`, and if so, returns `x` + fn as_x_minus_one(expr: &Expr) -> Option<&Expr> { + if let ExprKind::Binary(op, lhs, rhs) = &expr.kind { + if op.node == BinOpKind::Sub && Self::is_one(rhs) { + // x - 1 + return Some(lhs); + } else if op.node == BinOpKind::Add && Self::is_neg_one(lhs) { + // -1 + x + return Some(rhs); + } + } + None + } + fn check_binop<'tcx>(le_or_ge: LeOrGe, lhs: &'tcx Expr, rhs: &'tcx Expr) -> Option<(&'tcx Expr, &'tcx Expr)> { - match (le_or_ge, &lhs.kind, &rhs.kind) { - // case where `x - 1 >= ...` or `-1 + x >= ...` - (LeOrGe::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => { - match lhskind.node { - // `-1 + x` - BinOpKind::Add if Self::is_neg_one(lhslhs) => Some((lhsrhs, rhs)), - // `x - 1` - BinOpKind::Sub if Self::is_one(lhsrhs) => Some((lhslhs, rhs)), - _ => None, - } + match le_or_ge { + LeOrGe::Ge => { + // case where `x - 1 >= ...` or `-1 + x >= ...` + (Self::as_x_minus_one(lhs).map(|new_lhs| (new_lhs, rhs))) + // case where `... >= y + 1` or `... >= 1 + y` + .or_else(|| Self::as_x_plus_one(rhs).map(|new_rhs| (lhs, new_rhs))) }, - // case where `... >= y + 1` or `... >= 1 + y` - (LeOrGe::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => { - // `y + 1` and `1 + y` - if Self::is_one(rhslhs) { - Some((lhs, rhsrhs)) - } else if Self::is_one(rhsrhs) { - Some((lhs, rhslhs)) - } else { - None - } + LeOrGe::Le => { + // case where `x + 1 <= ...` or `1 + x <= ...` + (Self::as_x_plus_one(lhs).map(|new_lhs| (new_lhs, rhs))) + // case where `... <= y - 1` or `... <= -1 + y` + .or_else(|| Self::as_x_minus_one(rhs).map(|new_rhs| (lhs, new_rhs))) }, - // case where `x + 1 <= ...` or `1 + x <= ...` - (LeOrGe::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => { - // `1 + x` and `x + 1` - if Self::is_one(lhslhs) { - Some((lhsrhs, rhs)) - } else if Self::is_one(lhsrhs) { - Some((lhslhs, rhs)) - } else { - None - } - }, - // case where `... <= y - 1` or `... <= -1 + y` - (LeOrGe::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => { - match rhskind.node { - // `-1 + y` - BinOpKind::Add if Self::is_neg_one(rhslhs) => Some((lhs, rhsrhs)), - // `y - 1` - BinOpKind::Sub if Self::is_one(rhsrhs) => Some((lhs, rhslhs)), - _ => None, - } - }, - _ => None, } } From 306254b40e83189ae65fdda6501b1523d8c19c03 Mon Sep 17 00:00:00 2001 From: relaxcn Date: Sun, 11 Jan 2026 03:51:36 +0800 Subject: [PATCH 060/273] improve the doc of `doc_paragraphs_missing_punctuation` --- clippy_lints/src/doc/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 2b41275ee3a4..ecf7acbd7ce6 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -692,6 +692,12 @@ declare_clippy_lint! { /// /// /// /// It was chosen by a fair dice roll. /// ``` + /// + /// ### Terminal punctuation marks + /// This lint treats these characters as end markers: '.', '?', '!', '…' and ':'. + /// + /// The colon is not exactly a terminal punctuation mark, but this is required for paragraphs that + /// introduce a table or a list for example. #[clippy::version = "1.93.0"] pub DOC_PARAGRAPHS_MISSING_PUNCTUATION, restriction, From 47b987eccb7c2defece8f053c197836784baf421 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 11 Jan 2026 10:15:05 +0100 Subject: [PATCH 061/273] Do not show spans from external crates in `missing_trait_methods` Pointing to the missing method definition in external crates will use paths which depend on the system, and even on the Rust compiler version used when the iterator is defined in the standard library. When the trait being implemented and whose method is missing is external, point only to the trait implementation. The user will be able to figure out, or even navigate using their IDE, to the proper definition. As a side-effect, this will remove a large lintcheck churn at every Rustup for this lint. --- clippy_lints/src/missing_trait_methods.rs | 10 +++++++++- tests/ui/missing_trait_methods.rs | 7 +++++++ tests/ui/missing_trait_methods.stderr | 10 +++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs index 8e9400e9d583..7a791f949943 100644 --- a/clippy_lints/src/missing_trait_methods.rs +++ b/clippy_lints/src/missing_trait_methods.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_lint_allowed; use clippy_utils::macros::span_is_local; +use clippy_utils::source::snippet_opt; use rustc_hir::def_id::DefIdSet; use rustc_hir::{Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -84,7 +85,14 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods { cx.tcx.def_span(item.owner_id), format!("missing trait method provided by default: `{}`", assoc.name()), |diag| { - diag.span_help(cx.tcx.def_span(assoc.def_id), "implement the method"); + if assoc.def_id.is_local() { + diag.span_help(cx.tcx.def_span(assoc.def_id), "implement the method"); + } else if let Some(snippet) = snippet_opt(cx, of_trait.trait_ref.path.span) { + diag.help(format!( + "implement the missing `{}` method of the `{snippet}` trait", + assoc.name(), + )); + } }, ); } diff --git a/tests/ui/missing_trait_methods.rs b/tests/ui/missing_trait_methods.rs index 7af186ba3322..67070a299951 100644 --- a/tests/ui/missing_trait_methods.rs +++ b/tests/ui/missing_trait_methods.rs @@ -62,3 +62,10 @@ impl MissingMultiple for Partial {} //~| missing_trait_methods fn main() {} + +//~v missing_trait_methods +impl PartialEq for Partial { + fn eq(&self, other: &Partial) -> bool { + todo!() + } +} diff --git a/tests/ui/missing_trait_methods.stderr b/tests/ui/missing_trait_methods.stderr index d9fb8951ab3d..e5155ad587cb 100644 --- a/tests/ui/missing_trait_methods.stderr +++ b/tests/ui/missing_trait_methods.stderr @@ -60,5 +60,13 @@ help: implement the method LL | fn three() {} | ^^^^^^^^^^ -error: aborting due to 5 previous errors +error: missing trait method provided by default: `ne` + --> tests/ui/missing_trait_methods.rs:67:1 + | +LL | impl PartialEq for Partial { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: implement the missing `ne` method of the `PartialEq` trait + +error: aborting due to 6 previous errors From 8a71deb28275b847492208a325db589f2c222a49 Mon Sep 17 00:00:00 2001 From: Andre Bogus Date: Thu, 8 Jan 2026 23:26:12 +0100 Subject: [PATCH 062/273] add `manual_take` lint --- CHANGELOG.md | 1 + book/src/lint_configuration.md | 1 + clippy_config/src/conf.rs | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/manual_take.rs | 114 +++++++++++++++++++++++++++++ tests/ui/manual_take.fixed | 57 +++++++++++++++ tests/ui/manual_take.rs | 69 +++++++++++++++++ tests/ui/manual_take.stderr | 53 ++++++++++++++ tests/ui/manual_take_nocore.rs | 37 ++++++++++ tests/ui/manual_take_nocore.stderr | 0 tests/ui/manual_take_nostd.fixed | 10 +++ tests/ui/manual_take_nostd.rs | 22 ++++++ tests/ui/manual_take_nostd.stderr | 54 ++++++++++++++ 14 files changed, 422 insertions(+) create mode 100644 clippy_lints/src/manual_take.rs create mode 100644 tests/ui/manual_take.fixed create mode 100644 tests/ui/manual_take.rs create mode 100644 tests/ui/manual_take.stderr create mode 100644 tests/ui/manual_take_nocore.rs create mode 100644 tests/ui/manual_take_nocore.stderr create mode 100644 tests/ui/manual_take_nostd.fixed create mode 100644 tests/ui/manual_take_nostd.rs create mode 100644 tests/ui/manual_take_nostd.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cda8003b05c..b5172267fcaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6653,6 +6653,7 @@ Released 2018-09-13 [`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new [`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip [`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap +[`manual_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_take [`manual_try_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold [`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or [`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index a1c079898594..f81dd421f59b 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -905,6 +905,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_split_once`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once) * [`manual_str_repeat`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat) * [`manual_strip`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip) +* [`manual_take`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_take) * [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold) * [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone) * [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index e1d7c1d88eb9..849d9a613d80 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -780,6 +780,7 @@ define_Conf! { manual_split_once, manual_str_repeat, manual_strip, + manual_take, manual_try_fold, map_clone, map_unwrap_or, diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ef9da84c4407..549fc64d50e2 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -316,6 +316,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO, crate::manual_string_new::MANUAL_STRING_NEW_INFO, crate::manual_strip::MANUAL_STRIP_INFO, + crate::manual_take::MANUAL_TAKE_INFO, crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO, crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO, crate::match_result_ok::MATCH_RESULT_OK_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ef2461f8b097..eccff4789a20 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -219,6 +219,7 @@ mod manual_rotate; mod manual_slice_size_calculation; mod manual_string_new; mod manual_strip; +mod manual_take; mod map_unit_fn; mod match_result_ok; mod matches; @@ -861,6 +862,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))), Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)), Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))), + Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/manual_take.rs b/clippy_lints/src/manual_take.rs new file mode 100644 index 000000000000..8e74d04c4556 --- /dev/null +++ b/clippy_lints/src/manual_take.rs @@ -0,0 +1,114 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{MEM_TAKE, Msrv}; +use clippy_utils::source::snippet_with_context; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::impl_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Detects manual re-implementations of `std::mem::take`. + /// + /// ### Why is this bad? + /// Because the function call is shorter and easier to read. + /// + /// ### Known issues + /// Currently the lint only detects cases involving `bool`s. + /// + /// ### Example + /// ```no_run + /// let mut x = true; + /// let _ = if x { + /// x = false; + /// true + /// } else { + /// false + /// }; + /// ``` + /// Use instead: + /// ```no_run + /// let mut x = true; + /// let _ = std::mem::take(&mut x); + /// ``` + #[clippy::version = "1.94.0"] + pub MANUAL_TAKE, + complexity, + "manual `mem::take` implementation" +} +pub struct ManualTake { + msrv: Msrv, +} + +impl ManualTake { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} + +impl_lint_pass!(ManualTake => [MANUAL_TAKE]); + +impl LateLintPass<'_> for ManualTake { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::If(cond, then, Some(otherwise)) = expr.kind + && let ExprKind::Path(_) = cond.kind + && let ExprKind::Block( + Block { + stmts: [stmt], + expr: Some(then_expr), + .. + }, + .., + ) = then.kind + && let ExprKind::Block( + Block { + stmts: [], + expr: Some(else_expr), + .. + }, + .., + ) = otherwise.kind + && let StmtKind::Semi(assignment) = stmt.kind + && let ExprKind::Assign(mut_c, possible_false, _) = assignment.kind + && let ExprKind::Path(_) = mut_c.kind + && !expr.span.in_external_macro(cx.sess().source_map()) + && let Some(std_or_core) = clippy_utils::std_or_core(cx) + && self.msrv.meets(cx, MEM_TAKE) + && clippy_utils::SpanlessEq::new(cx).eq_expr(cond, mut_c) + && Some(false) == as_const_bool(possible_false) + && let Some(then_bool) = as_const_bool(then_expr) + && let Some(else_bool) = as_const_bool(else_expr) + && then_bool != else_bool + { + span_lint_and_then( + cx, + MANUAL_TAKE, + expr.span, + "manual implementation of `mem::take`", + |diag| { + let mut app = Applicability::MachineApplicable; + let negate = if then_bool { "" } else { "!" }; + let taken = snippet_with_context(cx, cond.span, expr.span.ctxt(), "_", &mut app).0; + diag.span_suggestion_verbose( + expr.span, + "use", + format!("{negate}{std_or_core}::mem::take(&mut {taken})"), + app, + ); + }, + ); + } + } +} + +fn as_const_bool(e: &Expr<'_>) -> Option { + if let ExprKind::Lit(lit) = e.kind + && let LitKind::Bool(b) = lit.node + { + Some(b) + } else { + None + } +} diff --git a/tests/ui/manual_take.fixed b/tests/ui/manual_take.fixed new file mode 100644 index 000000000000..5891bd49c81c --- /dev/null +++ b/tests/ui/manual_take.fixed @@ -0,0 +1,57 @@ +#![warn(clippy::manual_take)] + +fn main() { + msrv_1_39(); + msrv_1_40(); + let mut x = true; + let mut y = false; + + let _lint_negated = !std::mem::take(&mut x); + + let _ = if x { + y = false; + true + } else { + false + }; + + let _ = if x { + x = true; + true + } else { + false + }; + + let _ = if x { + x = false; + y = true; + false + } else { + true + }; + + let _ = if x { + x = false; + false + } else { + y = true; + true + }; +} + +#[clippy::msrv = "1.39.0"] +fn msrv_1_39() -> bool { + let mut x = true; + if x { + x = false; + true + } else { + false + } +} + +#[clippy::msrv = "1.40.0"] +fn msrv_1_40() -> bool { + let mut x = true; + std::mem::take(&mut x) +} diff --git a/tests/ui/manual_take.rs b/tests/ui/manual_take.rs new file mode 100644 index 000000000000..89903fcb2416 --- /dev/null +++ b/tests/ui/manual_take.rs @@ -0,0 +1,69 @@ +#![warn(clippy::manual_take)] + +fn main() { + msrv_1_39(); + msrv_1_40(); + let mut x = true; + let mut y = false; + + let _lint_negated = if x { + //~^ manual_take + x = false; + false + } else { + true + }; + + let _ = if x { + y = false; + true + } else { + false + }; + + let _ = if x { + x = true; + true + } else { + false + }; + + let _ = if x { + x = false; + y = true; + false + } else { + true + }; + + let _ = if x { + x = false; + false + } else { + y = true; + true + }; +} + +#[clippy::msrv = "1.39.0"] +fn msrv_1_39() -> bool { + let mut x = true; + if x { + x = false; + true + } else { + false + } +} + +#[clippy::msrv = "1.40.0"] +fn msrv_1_40() -> bool { + let mut x = true; + if x { + //~^ manual_take + x = false; + true + } else { + false + } +} diff --git a/tests/ui/manual_take.stderr b/tests/ui/manual_take.stderr new file mode 100644 index 000000000000..69ba7778a275 --- /dev/null +++ b/tests/ui/manual_take.stderr @@ -0,0 +1,53 @@ +error: manual implementation of `mem::take` + --> tests/ui/manual_take.rs:9:25 + | +LL | let _lint_negated = if x { + | _________________________^ +LL | | +LL | | x = false; +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ + | + = note: `-D clippy::manual-take` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_take)]` +help: use + | +LL - let _lint_negated = if x { +LL - +LL - x = false; +LL - false +LL - } else { +LL - true +LL - }; +LL + let _lint_negated = !std::mem::take(&mut x); + | + +error: manual implementation of `mem::take` + --> tests/ui/manual_take.rs:62:5 + | +LL | / if x { +LL | | +LL | | x = false; +LL | | true +LL | | } else { +LL | | false +LL | | } + | |_____^ + | +help: use + | +LL - if x { +LL - +LL - x = false; +LL - true +LL - } else { +LL - false +LL - } +LL + std::mem::take(&mut x) + | + +error: aborting due to 2 previous errors + diff --git a/tests/ui/manual_take_nocore.rs b/tests/ui/manual_take_nocore.rs new file mode 100644 index 000000000000..ef4f23395028 --- /dev/null +++ b/tests/ui/manual_take_nocore.rs @@ -0,0 +1,37 @@ +//@ check-pass +#![feature(no_core, lang_items)] +#![no_core] +#![allow(clippy::missing_safety_doc)] +#![warn(clippy::manual_take)] + +#[link(name = "c")] +unsafe extern "C" {} + +#[lang = "pointee_sized"] +pub trait PointeeSized {} + +#[lang = "meta_sized"] +pub trait MetaSized: PointeeSized {} + +#[lang = "sized"] +pub trait Sized: MetaSized {} +#[lang = "copy"] +pub trait Copy {} +#[lang = "freeze"] +pub unsafe trait Freeze {} + +#[lang = "start"] +fn start(_main: fn() -> T, _argc: isize, _argv: *const *const u8, _sigpipe: u8) -> isize { + 0 +} + +fn main() { + let mut x = true; + // this should not lint because we don't have std nor core + let _manual_take = if x { + x = false; + true + } else { + false + }; +} diff --git a/tests/ui/manual_take_nocore.stderr b/tests/ui/manual_take_nocore.stderr new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/ui/manual_take_nostd.fixed b/tests/ui/manual_take_nostd.fixed new file mode 100644 index 000000000000..123c5a91998c --- /dev/null +++ b/tests/ui/manual_take_nostd.fixed @@ -0,0 +1,10 @@ +#![no_std] +#![warn(clippy::manual_take)] + +pub fn manual_mem_take_should_reference_core() { + let mut x = true; + + let _lint_negated = !core::mem::take(&mut x); + + let _lint = core::mem::take(&mut x); +} diff --git a/tests/ui/manual_take_nostd.rs b/tests/ui/manual_take_nostd.rs new file mode 100644 index 000000000000..0df352477b74 --- /dev/null +++ b/tests/ui/manual_take_nostd.rs @@ -0,0 +1,22 @@ +#![no_std] +#![warn(clippy::manual_take)] + +pub fn manual_mem_take_should_reference_core() { + let mut x = true; + + let _lint_negated = if x { + //~^ manual_take + x = false; + false + } else { + true + }; + + let _lint = if x { + //~^ manual_take + x = false; + true + } else { + false + }; +} diff --git a/tests/ui/manual_take_nostd.stderr b/tests/ui/manual_take_nostd.stderr new file mode 100644 index 000000000000..71a5066e4db3 --- /dev/null +++ b/tests/ui/manual_take_nostd.stderr @@ -0,0 +1,54 @@ +error: manual implementation of `mem::take` + --> tests/ui/manual_take_nostd.rs:7:25 + | +LL | let _lint_negated = if x { + | _________________________^ +LL | | +LL | | x = false; +LL | | false +LL | | } else { +LL | | true +LL | | }; + | |_____^ + | + = note: `-D clippy::manual-take` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_take)]` +help: use + | +LL - let _lint_negated = if x { +LL - +LL - x = false; +LL - false +LL - } else { +LL - true +LL - }; +LL + let _lint_negated = !core::mem::take(&mut x); + | + +error: manual implementation of `mem::take` + --> tests/ui/manual_take_nostd.rs:15:17 + | +LL | let _lint = if x { + | _________________^ +LL | | +LL | | x = false; +LL | | true +LL | | } else { +LL | | false +LL | | }; + | |_____^ + | +help: use + | +LL - let _lint = if x { +LL - +LL - x = false; +LL - true +LL - } else { +LL - false +LL - }; +LL + let _lint = core::mem::take(&mut x); + | + +error: aborting due to 2 previous errors + From d419bddb96f8177a3d8a7cb3c4df15902a24028a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 9 Jan 2026 10:45:26 +1100 Subject: [PATCH 063/273] clippy: remove `ty_has_erased_regions`. It duplicates `has_erased_regions` from the compiler. --- .../matches/significant_drop_in_scrutinee.rs | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index bac35e7f8c70..14d265bfdad8 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -12,7 +12,7 @@ use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LintContext}; -use rustc_middle::ty::{GenericArgKind, Region, RegionKind, Ty, TyCtxt, TypeVisitable, TypeVisitor}; +use rustc_middle::ty::{GenericArgKind, RegionKind, Ty, TypeVisitableExt}; use rustc_span::Span; use super::SIGNIFICANT_DROP_IN_SCRUTINEE; @@ -303,13 +303,13 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { if self.sig_drop_holder != SigDropHolder::None { let parent_ty = self.cx.typeck_results().expr_ty(parent_expr); - if !ty_has_erased_regions(parent_ty) && !parent_expr.is_syntactic_place_expr() { + if !parent_ty.has_erased_regions() && !parent_expr.is_syntactic_place_expr() { self.replace_current_sig_drop(parent_expr.span, parent_ty.is_unit(), 0); self.sig_drop_holder = SigDropHolder::Moved; } let (peel_ref_ty, peel_ref_times) = ty_peel_refs(parent_ty); - if !ty_has_erased_regions(peel_ref_ty) && is_copy(self.cx, peel_ref_ty) { + if !peel_ref_ty.has_erased_regions() && is_copy(self.cx, peel_ref_ty) { self.replace_current_sig_drop(parent_expr.span, peel_ref_ty.is_unit(), peel_ref_times); self.sig_drop_holder = SigDropHolder::Moved; } @@ -399,24 +399,6 @@ fn ty_peel_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { (ty, n) } -fn ty_has_erased_regions(ty: Ty<'_>) -> bool { - struct V; - - impl<'tcx> TypeVisitor> for V { - type Result = ControlFlow<()>; - - fn visit_region(&mut self, region: Region<'tcx>) -> Self::Result { - if region.is_erased() { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - } - } - - ty.visit_with(&mut V).is_break() -} - impl<'tcx> Visitor<'tcx> for SigDropHelper<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { // We've emitted a lint on some neighborhood expression. That lint will suggest to move out the From 756e1bac6e1939dcd80125934fc6409846f02dac Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 11 Oct 2025 23:48:20 -0400 Subject: [PATCH 064/273] `clippy_dev`: Move lint uplifting into its own command. --- clippy_dev/src/deprecate_lint.rs | 54 ++++++++++++++++++++++++++++- clippy_dev/src/main.rs | 36 +++++++++----------- clippy_dev/src/rename_lint.rs | 58 +++++--------------------------- 3 files changed, 78 insertions(+), 70 deletions(-) diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 0401cfda7080..bee7508dabb9 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,4 +1,4 @@ -use crate::parse::{DeprecatedLint, Lint, ParseCx}; +use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint}; use crate::update_lints::generate_lint_files; use crate::utils::{UpdateMode, Version}; use std::ffi::OsStr; @@ -61,6 +61,58 @@ pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str, } } +pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) { + let mut lints = cx.find_lint_decls(); + let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); + + let Some(lint) = lints.iter().find(|l| l.name == old_name) else { + eprintln!("error: failed to find lint `{old_name}`"); + return; + }; + + let old_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", old_name]); + cx.arena.alloc_str(buf) + }); + for lint in &mut renamed_lints { + if lint.new_name == old_name_prefixed { + lint.new_name = new_name; + } + } + match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) { + Ok(_) => { + println!("`{old_name}` is already deprecated"); + return; + }, + Err(idx) => renamed_lints.insert( + idx, + RenamedLint { + old_name: old_name_prefixed, + new_name, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }, + ), + } + + let mod_path = { + let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); + if mod_path.is_dir() { + mod_path = mod_path.join("mod"); + } + + mod_path.set_extension("rs"); + mod_path + }; + + if remove_lint_declaration(old_name, &mod_path, &mut lints).unwrap_or(false) { + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{old_name}` has successfully been uplifted"); + println!("note: you must run `cargo uitest` to update the test results"); + } else { + eprintln!("error: lint not found"); + } +} + fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) -> io::Result { fn remove_lint(name: &str, lints: &mut Vec>) { lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 392c3aabf193..9571dfde1877 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -74,18 +74,11 @@ fn main() { }, DevCommand::Serve { port, lint } => serve::run(port, lint), DevCommand::Lint { path, edition, args } => lint::run(&path, &edition, args.iter()), - DevCommand::RenameLint { - old_name, - new_name, - uplift, - } => new_parse_cx(|cx| { - rename_lint::rename( - cx, - clippy.version, - &old_name, - new_name.as_ref().unwrap_or(&old_name), - uplift, - ); + DevCommand::RenameLint { old_name, new_name } => new_parse_cx(|cx| { + rename_lint::rename(cx, clippy.version, &old_name, &new_name); + }), + DevCommand::Uplift { old_name, new_name } => new_parse_cx(|cx| { + deprecate_lint::uplift(cx, clippy.version, &old_name, new_name.as_deref().unwrap_or(&old_name)); }), DevCommand::Deprecate { name, reason } => { new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason)); @@ -243,15 +236,9 @@ enum DevCommand { /// The name of the lint to rename #[arg(value_parser = lint_name)] old_name: String, - #[arg( - required_unless_present = "uplift", - value_parser = lint_name, - )] + #[arg(value_parser = lint_name)] /// The new name of the lint - new_name: Option, - #[arg(long)] - /// This lint will be uplifted into rustc - uplift: bool, + new_name: String, }, /// Deprecate the given lint Deprecate { @@ -266,6 +253,15 @@ enum DevCommand { Sync(SyncCommand), /// Manage Clippy releases Release(ReleaseCommand), + /// Marks a lint as uplifted into rustc and removes its code + Uplift { + /// The name of the lint to uplift + #[arg(value_parser = lint_name)] + old_name: String, + /// The name of the lint in rustc + #[arg(value_parser = lint_name)] + new_name: Option, + }, } #[derive(Args)] diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index 8e30eb7ce95b..9be4f2bdc970 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -25,8 +25,7 @@ use std::path::Path; /// * If either lint name has a prefix /// * If `old_name` doesn't name an existing lint. /// * If `old_name` names a deprecated or renamed lint. -#[expect(clippy::too_many_lines)] -pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str, uplift: bool) { +pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str) { let mut updater = FileUpdater::default(); let mut lints = cx.find_lint_decls(); let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); @@ -34,20 +33,15 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else { panic!("could not find lint `{old_name}`"); }; - let lint = &lints[lint_idx]; let old_name_prefixed = cx.str_buf.with(|buf| { buf.extend(["clippy::", old_name]); cx.arena.alloc_str(buf) }); - let new_name_prefixed = if uplift { - new_name - } else { - cx.str_buf.with(|buf| { - buf.extend(["clippy::", new_name]); - cx.arena.alloc_str(buf) - }) - }; + let new_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", new_name]); + cx.arena.alloc_str(buf) + }); for lint in &mut renamed_lints { if lint.new_name == old_name_prefixed { @@ -77,31 +71,7 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name)); let mut mod_edit = ModEdit::None; - if uplift { - let is_unique_mod = lints[..lint_idx].iter().any(|l| l.module == lint.module) - || lints[lint_idx + 1..].iter().any(|l| l.module == lint.module); - if is_unique_mod { - if delete_file_if_exists(lint.path.as_ref()) { - mod_edit = ModEdit::Delete; - } - } else { - updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.declaration_range.start]; - if start.ends_with("\n\n") { - start = &start[..start.len() - 1]; - } - let mut end = &src[lint.declaration_range.end..]; - if end.starts_with("\n\n") { - end = &end[1..]; - } - dst.push_str(start); - dst.push_str(end); - UpdateStatus::Changed - }); - } - delete_test_files(old_name, change_prefixed_tests); - lints.remove(lint_idx); - } else if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { + if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { let lint = &mut lints[lint_idx]; if lint.module.ends_with(old_name) && lint @@ -139,19 +109,9 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str } generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - if uplift { - println!("Uplifted `clippy::{old_name}` as `{new_name}`"); - if matches!(mod_edit, ModEdit::None) { - println!("Only the rename has been registered, the code will need to be edited manually"); - } else { - println!("All the lint's code has been deleted"); - println!("Make sure to inspect the results as some things may have been missed"); - } - } else { - println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); - println!("All code referencing the old name has been updated"); - println!("Make sure to inspect the results as some things may have been missed"); - } + println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); + println!("All code referencing the old name has been updated"); + println!("Make sure to inspect the results as some things may have been missed"); println!("note: `cargo uibless` still needs to be run to update the test results"); } From d9fa6d95151ff267094da06781ef655dd0f693dd Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 12 Oct 2025 14:43:21 -0400 Subject: [PATCH 065/273] `clippy_dev`: Rename `deprecate_lint` module to `edit_lints`. --- clippy_dev/src/edit_lints.rs | 213 +++++++++++++++++++++++++++++++++++ clippy_dev/src/lib.rs | 2 +- clippy_dev/src/main.rs | 8 +- 3 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 clippy_dev/src/edit_lints.rs diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs new file mode 100644 index 000000000000..573bd1e44535 --- /dev/null +++ b/clippy_dev/src/edit_lints.rs @@ -0,0 +1,213 @@ +use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint}; +use crate::update_lints::generate_lint_files; +use crate::utils::{UpdateMode, Version}; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Runs the `deprecate` command +/// +/// This does the following: +/// * Adds an entry to `deprecated_lints.rs`. +/// * Removes the lint declaration (and the entire file if applicable) +/// +/// # Panics +/// +/// If a file path could not read from or written to +pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'env str, reason: &'env str) { + let mut lints = cx.find_lint_decls(); + let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints(); + + let Some(lint) = lints.iter().find(|l| l.name == name) else { + eprintln!("error: failed to find lint `{name}`"); + return; + }; + + let prefixed_name = cx.str_buf.with(|buf| { + buf.extend(["clippy::", name]); + cx.arena.alloc_str(buf) + }); + match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) { + Ok(_) => { + println!("`{name}` is already deprecated"); + return; + }, + Err(idx) => deprecated_lints.insert( + idx, + DeprecatedLint { + name: prefixed_name, + reason, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }, + ), + } + + let mod_path = { + let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); + if mod_path.is_dir() { + mod_path = mod_path.join("mod"); + } + + mod_path.set_extension("rs"); + mod_path + }; + + if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) { + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{name}` has successfully been deprecated"); + println!("note: you must run `cargo uitest` to update the test results"); + } else { + eprintln!("error: lint not found"); + } +} + +pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) { + let mut lints = cx.find_lint_decls(); + let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); + + let Some(lint) = lints.iter().find(|l| l.name == old_name) else { + eprintln!("error: failed to find lint `{old_name}`"); + return; + }; + + let old_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", old_name]); + cx.arena.alloc_str(buf) + }); + for lint in &mut renamed_lints { + if lint.new_name == old_name_prefixed { + lint.new_name = new_name; + } + } + match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) { + Ok(_) => { + println!("`{old_name}` is already deprecated"); + return; + }, + Err(idx) => renamed_lints.insert( + idx, + RenamedLint { + old_name: old_name_prefixed, + new_name, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }, + ), + } + + let mod_path = { + let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); + if mod_path.is_dir() { + mod_path = mod_path.join("mod"); + } + + mod_path.set_extension("rs"); + mod_path + }; + + if remove_lint_declaration(old_name, &mod_path, &mut lints).unwrap_or(false) { + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{old_name}` has successfully been uplifted"); + println!("note: you must run `cargo uitest` to update the test results"); + } else { + eprintln!("error: lint not found"); + } +} + +fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) -> io::Result { + fn remove_lint(name: &str, lints: &mut Vec>) { + lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); + } + + fn remove_test_assets(name: &str) { + let test_file_stem = format!("tests/ui/{name}"); + let path = Path::new(&test_file_stem); + + // Some lints have their own directories, delete them + if path.is_dir() { + let _ = fs::remove_dir_all(path); + return; + } + + // Remove all related test files + let _ = fs::remove_file(path.with_extension("rs")); + let _ = fs::remove_file(path.with_extension("stderr")); + let _ = fs::remove_file(path.with_extension("fixed")); + } + + fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { + let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| { + content + .find("declare_lint_pass!") + .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`")) + }); + let mut impl_lint_pass_end = content[impl_lint_pass_start..] + .find(']') + .expect("failed to find `impl_lint_pass` terminator"); + + impl_lint_pass_end += impl_lint_pass_start; + if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) { + let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); + for c in content[lint_name_end..impl_lint_pass_end].chars() { + // Remove trailing whitespace + if c == ',' || c.is_whitespace() { + lint_name_end += 1; + } else { + break; + } + } + + content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, ""); + } + } + + if path.exists() + && let Some(lint) = lints.iter().find(|l| l.name == name) + { + if lint.module == name { + // The lint name is the same as the file, we can just delete the entire file + fs::remove_file(path)?; + } else { + // We can't delete the entire file, just remove the declaration + + if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { + // Remove clippy_lints/src/some_mod/some_lint.rs + let mut lint_mod_path = path.to_path_buf(); + lint_mod_path.set_file_name(name); + lint_mod_path.set_extension("rs"); + + let _ = fs::remove_file(lint_mod_path); + } + + let mut content = + fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); + + eprintln!( + "warn: you will have to manually remove any code related to `{name}` from `{}`", + path.display() + ); + + assert!( + content[lint.declaration_range].contains(&name.to_uppercase()), + "error: `{}` does not contain lint `{}`'s declaration", + path.display(), + lint.name + ); + + // Remove lint declaration (declare_clippy_lint!) + content.replace_range(lint.declaration_range, ""); + + // Remove the module declaration (mod xyz;) + let mod_decl = format!("\nmod {name};"); + content = content.replacen(&mod_decl, "", 1); + + remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); + fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); + } + + remove_test_assets(name); + remove_lint(name, lints); + return Ok(true); + } + + Ok(false) +} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index cd103908be03..db9b230f6e59 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -23,8 +23,8 @@ extern crate rustc_arena; extern crate rustc_driver; extern crate rustc_lexer; -pub mod deprecate_lint; pub mod dogfood; +pub mod edit_lints; pub mod fmt; pub mod lint; pub mod new_lint; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 9571dfde1877..e4e19a100c6e 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -4,8 +4,8 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ - ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve, - setup, sync, update_lints, + ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve, setup, + sync, update_lints, }; use std::env; @@ -78,10 +78,10 @@ fn main() { rename_lint::rename(cx, clippy.version, &old_name, &new_name); }), DevCommand::Uplift { old_name, new_name } => new_parse_cx(|cx| { - deprecate_lint::uplift(cx, clippy.version, &old_name, new_name.as_deref().unwrap_or(&old_name)); + edit_lints::uplift(cx, clippy.version, &old_name, new_name.as_deref().unwrap_or(&old_name)); }), DevCommand::Deprecate { name, reason } => { - new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason)); + new_parse_cx(|cx| edit_lints::deprecate(cx, clippy.version, &name, &reason)); }, DevCommand::Sync(SyncCommand { subcommand }) => match subcommand { SyncSubcommand::UpdateNightly => sync::update_nightly(), From 8d4e435f492a54c14c8620ef665cdec4710abd19 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 12 Oct 2025 14:44:25 -0400 Subject: [PATCH 066/273] `clippy_dev`: Move `rename_lint` command to `edit_lints` module. --- clippy_dev/src/edit_lints.rs | 351 ++++++++++++++++++++++++++++++++- clippy_dev/src/lib.rs | 1 - clippy_dev/src/main.rs | 6 +- clippy_dev/src/rename_lint.rs | 353 ---------------------------------- 4 files changed, 352 insertions(+), 359 deletions(-) delete mode 100644 clippy_dev/src/rename_lint.rs diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs index 573bd1e44535..8a5ba26fd215 100644 --- a/clippy_dev/src/edit_lints.rs +++ b/clippy_dev/src/edit_lints.rs @@ -1,7 +1,12 @@ +use crate::parse::cursor::{self, Capture, Cursor}; use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint}; use crate::update_lints::generate_lint_files; -use crate::utils::{UpdateMode, Version}; -use std::ffi::OsStr; +use crate::utils::{ + ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, + expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, +}; +use rustc_lexer::TokenKind; +use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use std::{fs, io}; @@ -113,6 +118,111 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam } } +/// Runs the `rename_lint` command. +/// +/// This does the following: +/// * Adds an entry to `renamed_lints.rs`. +/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`). +/// * Renames the lint struct to the new name. +/// * Renames the module containing the lint struct to the new name if it shares a name with the +/// lint. +/// +/// # Panics +/// Panics for the following conditions: +/// * If a file path could not read from or then written to +/// * If either lint name has a prefix +/// * If `old_name` doesn't name an existing lint. +/// * If `old_name` names a deprecated or renamed lint. +pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) { + let mut updater = FileUpdater::default(); + let mut lints = cx.find_lint_decls(); + let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); + + let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else { + panic!("could not find lint `{old_name}`"); + }; + + let old_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", old_name]); + cx.arena.alloc_str(buf) + }); + let new_name_prefixed = cx.str_buf.with(|buf| { + buf.extend(["clippy::", new_name]); + cx.arena.alloc_str(buf) + }); + + for lint in &mut renamed_lints { + if lint.new_name == old_name_prefixed { + lint.new_name = new_name_prefixed; + } + } + match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) { + Ok(_) => { + println!("`{old_name}` already has a rename registered"); + return; + }, + Err(idx) => { + renamed_lints.insert( + idx, + RenamedLint { + old_name: old_name_prefixed, + new_name: new_name_prefixed, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }, + ); + }, + } + + // Some tests are named `lint_name_suffix` which should also be renamed, + // but we can't do that if the renamed lint's name overlaps with another + // lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists. + let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name)); + + let mut mod_edit = ModEdit::None; + if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { + let lint = &mut lints[lint_idx]; + if lint.module.ends_with(old_name) + && lint + .path + .file_stem() + .is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes()) + { + let mut new_path = lint.path.with_file_name(new_name).into_os_string(); + new_path.push(".rs"); + if try_rename_file(lint.path.as_ref(), new_path.as_ref()) { + mod_edit = ModEdit::Rename; + } + + lint.module = cx.str_buf.with(|buf| { + buf.push_str(&lint.module[..lint.module.len() - old_name.len()]); + buf.push_str(new_name); + cx.arena.alloc_str(buf) + }); + } + rename_test_files(old_name, new_name, change_prefixed_tests); + lints[lint_idx].name = new_name; + lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); + } else { + println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); + println!("Since `{new_name}` already exists the existing code has not been changed"); + return; + } + + let mut update_fn = file_update_fn(old_name, new_name, mod_edit); + for e in walk_dir_no_dot_or_target(".") { + let e = expect_action(e, ErrAction::Read, "."); + if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { + updater.update_file(e.path(), &mut update_fn); + } + } + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + + println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); + println!("All code referencing the old name has been updated"); + println!("Make sure to inspect the results as some things may have been missed"); + println!("note: `cargo uibless` still needs to be run to update the test results"); +} + fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) -> io::Result { fn remove_lint(name: &str, lints: &mut Vec>) { lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); @@ -211,3 +321,240 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) - Ok(false) } + +#[derive(Clone, Copy)] +enum ModEdit { + None, + Delete, + Rename, +} + +fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { + for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") { + let e = e.expect("error reading `tests/ui`"); + let name = e.file_name(); + if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') { + if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) { + dst.push((name, true)); + } + } else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len()) + { + dst.push((name, false)); + } + } +} + +fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { + if rename_prefixed { + for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { + let e = e.expect("error reading `tests/ui-toml`"); + let name = e.file_name(); + if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) { + dst.push((name, false)); + } + } + } else { + dst.push((lint.into(), false)); + } +} + +/// Renames all test files for the given lint. +/// +/// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix. +fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { + let mut tests = Vec::new(); + + let mut old_buf = OsString::from("tests/ui/"); + let mut new_buf = OsString::from("tests/ui/"); + collect_ui_test_names(old_name, rename_prefixed, &mut tests); + for &(ref name, is_file) in &tests { + old_buf.push(name); + new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); + if is_file { + try_rename_file(old_buf.as_ref(), new_buf.as_ref()); + } else { + try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); + } + old_buf.truncate("tests/ui/".len()); + new_buf.truncate("tests/ui/".len()); + } + + tests.clear(); + old_buf.truncate("tests/ui".len()); + new_buf.truncate("tests/ui".len()); + old_buf.push("-toml/"); + new_buf.push("-toml/"); + collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests); + for (name, _) in &tests { + old_buf.push(name); + new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); + try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); + old_buf.truncate("tests/ui/".len()); + new_buf.truncate("tests/ui/".len()); + } +} + +fn delete_test_files(lint: &str, rename_prefixed: bool) { + let mut tests = Vec::new(); + + let mut buf = OsString::from("tests/ui/"); + collect_ui_test_names(lint, rename_prefixed, &mut tests); + for &(ref name, is_file) in &tests { + buf.push(name); + if is_file { + delete_file_if_exists(buf.as_ref()); + } else { + delete_dir_if_exists(buf.as_ref()); + } + buf.truncate("tests/ui/".len()); + } + + buf.truncate("tests/ui".len()); + buf.push("-toml/"); + + tests.clear(); + collect_ui_toml_test_names(lint, rename_prefixed, &mut tests); + for (name, _) in &tests { + buf.push(name); + delete_dir_if_exists(buf.as_ref()); + buf.truncate("tests/ui/".len()); + } +} + +fn snake_to_pascal(s: &str) -> String { + let mut dst = Vec::with_capacity(s.len()); + let mut iter = s.bytes(); + || -> Option<()> { + dst.push(iter.next()?.to_ascii_uppercase()); + while let Some(c) = iter.next() { + if c == b'_' { + dst.push(iter.next()?.to_ascii_uppercase()); + } else { + dst.push(c); + } + } + Some(()) + }(); + String::from_utf8(dst).unwrap() +} + +#[expect(clippy::too_many_lines)] +fn file_update_fn<'a, 'b>( + old_name: &'a str, + new_name: &'b str, + mod_edit: ModEdit, +) -> impl use<'a, 'b> + FnMut(&Path, &str, &mut String) -> UpdateStatus { + let old_name_pascal = snake_to_pascal(old_name); + let new_name_pascal = snake_to_pascal(new_name); + let old_name_upper = old_name.to_ascii_uppercase(); + let new_name_upper = new_name.to_ascii_uppercase(); + move |_, src, dst| { + let mut copy_pos = 0u32; + let mut changed = false; + let mut cursor = Cursor::new(src); + let mut captures = [Capture::EMPTY]; + loop { + match cursor.peek() { + TokenKind::Eof => break, + TokenKind::Ident => { + let match_start = cursor.pos(); + let text = cursor.peek_text(); + cursor.step(); + match text { + // clippy::line_name or clippy::lint-name + "clippy" => { + if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + && cursor.get_text(captures[0]) == old_name + { + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); + dst.push_str(new_name); + copy_pos = cursor.pos(); + changed = true; + } + }, + // mod lint_name + "mod" => { + if !matches!(mod_edit, ModEdit::None) + && let Some(pos) = cursor.find_ident(old_name) + { + match mod_edit { + ModEdit::Rename => { + dst.push_str(&src[copy_pos as usize..pos as usize]); + dst.push_str(new_name); + copy_pos = cursor.pos(); + changed = true; + }, + ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => { + let mut start = &src[copy_pos as usize..match_start as usize]; + if start.ends_with("\n\n") { + start = &start[..start.len() - 1]; + } + dst.push_str(start); + copy_pos = cursor.pos(); + if src[copy_pos as usize..].starts_with("\n\n") { + copy_pos += 1; + } + changed = true; + }, + ModEdit::Delete | ModEdit::None => {}, + } + } + }, + // lint_name:: + name if matches!(mod_edit, ModEdit::Rename) && name == old_name => { + let name_end = cursor.pos(); + if cursor.match_pat(cursor::Pat::DoubleColon) { + dst.push_str(&src[copy_pos as usize..match_start as usize]); + dst.push_str(new_name); + copy_pos = name_end; + changed = true; + } + }, + // LINT_NAME or LintName + name => { + let replacement = if name == old_name_upper { + &new_name_upper + } else if name == old_name_pascal { + &new_name_pascal + } else { + continue; + }; + dst.push_str(&src[copy_pos as usize..match_start as usize]); + dst.push_str(replacement); + copy_pos = cursor.pos(); + changed = true; + }, + } + }, + // //~ lint_name + TokenKind::LineComment { doc_style: None } => { + let text = cursor.peek_text(); + if text.starts_with("//~") + && let Some(text) = text.strip_suffix(old_name) + && !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')) + { + dst.push_str(&src[copy_pos as usize..cursor.pos() as usize + text.len()]); + dst.push_str(new_name); + copy_pos = cursor.pos() + cursor.peek_len(); + changed = true; + } + cursor.step(); + }, + // ::lint_name + TokenKind::Colon + if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + && cursor.get_text(captures[0]) == old_name => + { + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); + dst.push_str(new_name); + copy_pos = cursor.pos(); + changed = true; + }, + _ => cursor.step(), + } + } + + dst.push_str(&src[copy_pos as usize..]); + UpdateStatus::from_changed(changed) + } +} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index db9b230f6e59..69309403c8d0 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -29,7 +29,6 @@ pub mod fmt; pub mod lint; pub mod new_lint; pub mod release; -pub mod rename_lint; pub mod serve; pub mod setup; pub mod sync; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index e4e19a100c6e..8dc2290df8e4 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -4,8 +4,8 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ - ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve, setup, - sync, update_lints, + ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, serve, setup, sync, + update_lints, }; use std::env; @@ -75,7 +75,7 @@ fn main() { DevCommand::Serve { port, lint } => serve::run(port, lint), DevCommand::Lint { path, edition, args } => lint::run(&path, &edition, args.iter()), DevCommand::RenameLint { old_name, new_name } => new_parse_cx(|cx| { - rename_lint::rename(cx, clippy.version, &old_name, &new_name); + edit_lints::rename(cx, clippy.version, &old_name, &new_name); }), DevCommand::Uplift { old_name, new_name } => new_parse_cx(|cx| { edit_lints::uplift(cx, clippy.version, &old_name, new_name.as_deref().unwrap_or(&old_name)); diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs deleted file mode 100644 index 9be4f2bdc970..000000000000 --- a/clippy_dev/src/rename_lint.rs +++ /dev/null @@ -1,353 +0,0 @@ -use crate::parse::cursor::{self, Capture, Cursor}; -use crate::parse::{ParseCx, RenamedLint}; -use crate::update_lints::generate_lint_files; -use crate::utils::{ - ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, - expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, -}; -use rustc_lexer::TokenKind; -use std::ffi::OsString; -use std::fs; -use std::path::Path; - -/// Runs the `rename_lint` command. -/// -/// This does the following: -/// * Adds an entry to `renamed_lints.rs`. -/// * Renames all lint attributes to the new name (e.g. `#[allow(clippy::lint_name)]`). -/// * Renames the lint struct to the new name. -/// * Renames the module containing the lint struct to the new name if it shares a name with the -/// lint. -/// -/// # Panics -/// Panics for the following conditions: -/// * If a file path could not read from or then written to -/// * If either lint name has a prefix -/// * If `old_name` doesn't name an existing lint. -/// * If `old_name` names a deprecated or renamed lint. -pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str) { - let mut updater = FileUpdater::default(); - let mut lints = cx.find_lint_decls(); - let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); - - let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else { - panic!("could not find lint `{old_name}`"); - }; - - let old_name_prefixed = cx.str_buf.with(|buf| { - buf.extend(["clippy::", old_name]); - cx.arena.alloc_str(buf) - }); - let new_name_prefixed = cx.str_buf.with(|buf| { - buf.extend(["clippy::", new_name]); - cx.arena.alloc_str(buf) - }); - - for lint in &mut renamed_lints { - if lint.new_name == old_name_prefixed { - lint.new_name = new_name_prefixed; - } - } - match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) { - Ok(_) => { - println!("`{old_name}` already has a rename registered"); - return; - }, - Err(idx) => { - renamed_lints.insert( - idx, - RenamedLint { - old_name: old_name_prefixed, - new_name: new_name_prefixed, - version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), - }, - ); - }, - } - - // Some tests are named `lint_name_suffix` which should also be renamed, - // but we can't do that if the renamed lint's name overlaps with another - // lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists. - let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name)); - - let mut mod_edit = ModEdit::None; - if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { - let lint = &mut lints[lint_idx]; - if lint.module.ends_with(old_name) - && lint - .path - .file_stem() - .is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes()) - { - let mut new_path = lint.path.with_file_name(new_name).into_os_string(); - new_path.push(".rs"); - if try_rename_file(lint.path.as_ref(), new_path.as_ref()) { - mod_edit = ModEdit::Rename; - } - - lint.module = cx.str_buf.with(|buf| { - buf.push_str(&lint.module[..lint.module.len() - old_name.len()]); - buf.push_str(new_name); - cx.arena.alloc_str(buf) - }); - } - rename_test_files(old_name, new_name, change_prefixed_tests); - lints[lint_idx].name = new_name; - lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); - } else { - println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); - println!("Since `{new_name}` already exists the existing code has not been changed"); - return; - } - - let mut update_fn = file_update_fn(old_name, new_name, mod_edit); - for e in walk_dir_no_dot_or_target(".") { - let e = expect_action(e, ErrAction::Read, "."); - if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { - updater.update_file(e.path(), &mut update_fn); - } - } - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - - println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); - println!("All code referencing the old name has been updated"); - println!("Make sure to inspect the results as some things may have been missed"); - println!("note: `cargo uibless` still needs to be run to update the test results"); -} - -#[derive(Clone, Copy)] -enum ModEdit { - None, - Delete, - Rename, -} - -fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { - for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") { - let e = e.expect("error reading `tests/ui`"); - let name = e.file_name(); - if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') { - if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) { - dst.push((name, true)); - } - } else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len()) - { - dst.push((name, false)); - } - } -} - -fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { - if rename_prefixed { - for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { - let e = e.expect("error reading `tests/ui-toml`"); - let name = e.file_name(); - if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) { - dst.push((name, false)); - } - } - } else { - dst.push((lint.into(), false)); - } -} - -/// Renames all test files for the given lint. -/// -/// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix. -fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { - let mut tests = Vec::new(); - - let mut old_buf = OsString::from("tests/ui/"); - let mut new_buf = OsString::from("tests/ui/"); - collect_ui_test_names(old_name, rename_prefixed, &mut tests); - for &(ref name, is_file) in &tests { - old_buf.push(name); - new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); - if is_file { - try_rename_file(old_buf.as_ref(), new_buf.as_ref()); - } else { - try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); - } - old_buf.truncate("tests/ui/".len()); - new_buf.truncate("tests/ui/".len()); - } - - tests.clear(); - old_buf.truncate("tests/ui".len()); - new_buf.truncate("tests/ui".len()); - old_buf.push("-toml/"); - new_buf.push("-toml/"); - collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests); - for (name, _) in &tests { - old_buf.push(name); - new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); - try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); - old_buf.truncate("tests/ui/".len()); - new_buf.truncate("tests/ui/".len()); - } -} - -fn delete_test_files(lint: &str, rename_prefixed: bool) { - let mut tests = Vec::new(); - - let mut buf = OsString::from("tests/ui/"); - collect_ui_test_names(lint, rename_prefixed, &mut tests); - for &(ref name, is_file) in &tests { - buf.push(name); - if is_file { - delete_file_if_exists(buf.as_ref()); - } else { - delete_dir_if_exists(buf.as_ref()); - } - buf.truncate("tests/ui/".len()); - } - - buf.truncate("tests/ui".len()); - buf.push("-toml/"); - - tests.clear(); - collect_ui_toml_test_names(lint, rename_prefixed, &mut tests); - for (name, _) in &tests { - buf.push(name); - delete_dir_if_exists(buf.as_ref()); - buf.truncate("tests/ui/".len()); - } -} - -fn snake_to_pascal(s: &str) -> String { - let mut dst = Vec::with_capacity(s.len()); - let mut iter = s.bytes(); - || -> Option<()> { - dst.push(iter.next()?.to_ascii_uppercase()); - while let Some(c) = iter.next() { - if c == b'_' { - dst.push(iter.next()?.to_ascii_uppercase()); - } else { - dst.push(c); - } - } - Some(()) - }(); - String::from_utf8(dst).unwrap() -} - -#[expect(clippy::too_many_lines)] -fn file_update_fn<'a, 'b>( - old_name: &'a str, - new_name: &'b str, - mod_edit: ModEdit, -) -> impl use<'a, 'b> + FnMut(&Path, &str, &mut String) -> UpdateStatus { - let old_name_pascal = snake_to_pascal(old_name); - let new_name_pascal = snake_to_pascal(new_name); - let old_name_upper = old_name.to_ascii_uppercase(); - let new_name_upper = new_name.to_ascii_uppercase(); - move |_, src, dst| { - let mut copy_pos = 0u32; - let mut changed = false; - let mut cursor = Cursor::new(src); - let mut captures = [Capture::EMPTY]; - loop { - match cursor.peek() { - TokenKind::Eof => break, - TokenKind::Ident => { - let match_start = cursor.pos(); - let text = cursor.peek_text(); - cursor.step(); - match text { - // clippy::line_name or clippy::lint-name - "clippy" => { - if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) - && cursor.get_text(captures[0]) == old_name - { - dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); - dst.push_str(new_name); - copy_pos = cursor.pos(); - changed = true; - } - }, - // mod lint_name - "mod" => { - if !matches!(mod_edit, ModEdit::None) - && let Some(pos) = cursor.find_ident(old_name) - { - match mod_edit { - ModEdit::Rename => { - dst.push_str(&src[copy_pos as usize..pos as usize]); - dst.push_str(new_name); - copy_pos = cursor.pos(); - changed = true; - }, - ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => { - let mut start = &src[copy_pos as usize..match_start as usize]; - if start.ends_with("\n\n") { - start = &start[..start.len() - 1]; - } - dst.push_str(start); - copy_pos = cursor.pos(); - if src[copy_pos as usize..].starts_with("\n\n") { - copy_pos += 1; - } - changed = true; - }, - ModEdit::Delete | ModEdit::None => {}, - } - } - }, - // lint_name:: - name if matches!(mod_edit, ModEdit::Rename) && name == old_name => { - let name_end = cursor.pos(); - if cursor.match_pat(cursor::Pat::DoubleColon) { - dst.push_str(&src[copy_pos as usize..match_start as usize]); - dst.push_str(new_name); - copy_pos = name_end; - changed = true; - } - }, - // LINT_NAME or LintName - name => { - let replacement = if name == old_name_upper { - &new_name_upper - } else if name == old_name_pascal { - &new_name_pascal - } else { - continue; - }; - dst.push_str(&src[copy_pos as usize..match_start as usize]); - dst.push_str(replacement); - copy_pos = cursor.pos(); - changed = true; - }, - } - }, - // //~ lint_name - TokenKind::LineComment { doc_style: None } => { - let text = cursor.peek_text(); - if text.starts_with("//~") - && let Some(text) = text.strip_suffix(old_name) - && !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')) - { - dst.push_str(&src[copy_pos as usize..cursor.pos() as usize + text.len()]); - dst.push_str(new_name); - copy_pos = cursor.pos() + cursor.peek_len(); - changed = true; - } - cursor.step(); - }, - // ::lint_name - TokenKind::Colon - if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) - && cursor.get_text(captures[0]) == old_name => - { - dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); - dst.push_str(new_name); - copy_pos = cursor.pos(); - changed = true; - }, - _ => cursor.step(), - } - } - - dst.push_str(&src[copy_pos as usize..]); - UpdateStatus::from_changed(changed) - } -} From 31db6aa5b24d9bf1751bf1017450abd4158bc06d Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 12 Oct 2025 22:42:33 -0400 Subject: [PATCH 067/273] `clippy_dev`: When renaming a lint better handle the case where the renamed lint is a prefix of another lint name. --- clippy_dev/src/edit_lints.rs | 79 ++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs index 8a5ba26fd215..14dd8b21df9a 100644 --- a/clippy_dev/src/edit_lints.rs +++ b/clippy_dev/src/edit_lints.rs @@ -173,11 +173,6 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam }, } - // Some tests are named `lint_name_suffix` which should also be renamed, - // but we can't do that if the renamed lint's name overlaps with another - // lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists. - let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name)); - let mut mod_edit = ModEdit::None; if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { let lint = &mut lints[lint_idx]; @@ -199,7 +194,16 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam cx.arena.alloc_str(buf) }); } - rename_test_files(old_name, new_name, change_prefixed_tests); + + rename_test_files( + old_name, + new_name, + &lints[lint_idx + 1..] + .iter() + .map(|l| l.name) + .take_while(|&n| n.starts_with(old_name)) + .collect::>(), + ); lints[lint_idx].name = new_name; lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name)); } else { @@ -329,44 +333,45 @@ enum ModEdit { Rename, } -fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { +fn collect_ui_test_names(lint: &str, ignored_prefixes: &[&str], dst: &mut Vec<(OsString, bool)>) { for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") { let e = e.expect("error reading `tests/ui`"); let name = e.file_name(); - if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') { - if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) { - dst.push((name, true)); - } - } else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len()) + if name.as_encoded_bytes().starts_with(lint.as_bytes()) + && !ignored_prefixes + .iter() + .any(|&pre| name.as_encoded_bytes().starts_with(pre.as_bytes())) + && let Ok(ty) = e.file_type() + && (ty.is_file() || ty.is_dir()) + { + dst.push((name, ty.is_file())); + } + } +} + +fn collect_ui_toml_test_names(lint: &str, ignored_prefixes: &[&str], dst: &mut Vec<(OsString, bool)>) { + for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { + let e = e.expect("error reading `tests/ui-toml`"); + let name = e.file_name(); + if name.as_encoded_bytes().starts_with(lint.as_bytes()) + && !ignored_prefixes + .iter() + .any(|&pre| name.as_encoded_bytes().starts_with(pre.as_bytes())) + && e.file_type().is_ok_and(|ty| ty.is_dir()) { dst.push((name, false)); } } } -fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { - if rename_prefixed { - for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { - let e = e.expect("error reading `tests/ui-toml`"); - let name = e.file_name(); - if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) { - dst.push((name, false)); - } - } - } else { - dst.push((lint.into(), false)); - } -} - -/// Renames all test files for the given lint. -/// -/// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix. -fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { - let mut tests = Vec::new(); +/// Renames all test files for the given lint where the file name does not start with any +/// of the given prefixes. +fn rename_test_files(old_name: &str, new_name: &str, ignored_prefixes: &[&str]) { + let mut tests: Vec<(OsString, bool)> = Vec::new(); let mut old_buf = OsString::from("tests/ui/"); let mut new_buf = OsString::from("tests/ui/"); - collect_ui_test_names(old_name, rename_prefixed, &mut tests); + collect_ui_test_names(old_name, ignored_prefixes, &mut tests); for &(ref name, is_file) in &tests { old_buf.push(name); new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); @@ -384,7 +389,7 @@ fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { new_buf.truncate("tests/ui".len()); old_buf.push("-toml/"); new_buf.push("-toml/"); - collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests); + collect_ui_toml_test_names(old_name, ignored_prefixes, &mut tests); for (name, _) in &tests { old_buf.push(name); new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); @@ -394,11 +399,13 @@ fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { } } -fn delete_test_files(lint: &str, rename_prefixed: bool) { +/// Deletes all test files for the given lint where the file name does not start with any +/// of the given prefixes. +fn delete_test_files(lint: &str, ignored_prefixes: &[&str]) { let mut tests = Vec::new(); let mut buf = OsString::from("tests/ui/"); - collect_ui_test_names(lint, rename_prefixed, &mut tests); + collect_ui_test_names(lint, ignored_prefixes, &mut tests); for &(ref name, is_file) in &tests { buf.push(name); if is_file { @@ -413,7 +420,7 @@ fn delete_test_files(lint: &str, rename_prefixed: bool) { buf.push("-toml/"); tests.clear(); - collect_ui_toml_test_names(lint, rename_prefixed, &mut tests); + collect_ui_toml_test_names(lint, ignored_prefixes, &mut tests); for (name, _) in &tests { buf.push(name); delete_dir_if_exists(buf.as_ref()); From 5e8d1b541c3e89587e8b07cd75208ea5e029af6a Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 13 Oct 2025 17:31:51 -0400 Subject: [PATCH 068/273] `clippy_dev`: Re-enable deleting the module declaration on deprecation and uplifting. --- clippy_dev/src/edit_lints.rs | 273 ++++++++++++--------------------- clippy_dev/src/parse/cursor.rs | 16 ++ 2 files changed, 117 insertions(+), 172 deletions(-) diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs index 14dd8b21df9a..fb1c1458c50c 100644 --- a/clippy_dev/src/edit_lints.rs +++ b/clippy_dev/src/edit_lints.rs @@ -6,9 +6,9 @@ use crate::utils::{ expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, }; use rustc_lexer::TokenKind; -use std::ffi::{OsStr, OsString}; -use std::path::{Path, PathBuf}; -use std::{fs, io}; +use std::ffi::OsString; +use std::fs; +use std::path::Path; /// Runs the `deprecate` command /// @@ -23,7 +23,7 @@ pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name let mut lints = cx.find_lint_decls(); let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints(); - let Some(lint) = lints.iter().find(|l| l.name == name) else { + let Some(lint_idx) = lints.iter().position(|l| l.name == name) else { eprintln!("error: failed to find lint `{name}`"); return; }; @@ -47,30 +47,17 @@ pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name ), } - let mod_path = { - let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); - if mod_path.is_dir() { - mod_path = mod_path.join("mod"); - } - - mod_path.set_extension("rs"); - mod_path - }; - - if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) { - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - println!("info: `{name}` has successfully been deprecated"); - println!("note: you must run `cargo uitest` to update the test results"); - } else { - eprintln!("error: lint not found"); - } + remove_lint_declaration(lint_idx, &mut lints, &mut FileUpdater::default()); + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{name}` has successfully been deprecated"); + println!("note: you must run `cargo uitest` to update the test results"); } pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) { let mut lints = cx.find_lint_decls(); let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints(); - let Some(lint) = lints.iter().find(|l| l.name == old_name) else { + let Some(lint_idx) = lints.iter().position(|l| l.name == old_name) else { eprintln!("error: failed to find lint `{old_name}`"); return; }; @@ -99,23 +86,18 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam ), } - let mod_path = { - let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); - if mod_path.is_dir() { - mod_path = mod_path.join("mod"); + let mut updater = FileUpdater::default(); + let remove_mod = remove_lint_declaration(lint_idx, &mut lints, &mut updater); + let mut update_fn = uplift_update_fn(old_name, new_name, remove_mod); + for e in walk_dir_no_dot_or_target(".") { + let e = expect_action(e, ErrAction::Read, "."); + if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { + updater.update_file(e.path(), &mut update_fn); } - - mod_path.set_extension("rs"); - mod_path - }; - - if remove_lint_declaration(old_name, &mod_path, &mut lints).unwrap_or(false) { - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - println!("info: `{old_name}` has successfully been uplifted"); - println!("note: you must run `cargo uitest` to update the test results"); - } else { - eprintln!("error: lint not found"); } + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{old_name}` has successfully been uplifted as `{new_name}`"); + println!("note: you must run `cargo uitest` to update the test results"); } /// Runs the `rename_lint` command. @@ -173,7 +155,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam }, } - let mut mod_edit = ModEdit::None; + let mut rename_mod = false; if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() { let lint = &mut lints[lint_idx]; if lint.module.ends_with(old_name) @@ -185,7 +167,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam let mut new_path = lint.path.with_file_name(new_name).into_os_string(); new_path.push(".rs"); if try_rename_file(lint.path.as_ref(), new_path.as_ref()) { - mod_edit = ModEdit::Rename; + rename_mod = true; } lint.module = cx.str_buf.with(|buf| { @@ -212,7 +194,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam return; } - let mut update_fn = file_update_fn(old_name, new_name, mod_edit); + let mut update_fn = rename_update_fn(old_name, new_name, rename_mod); for e in walk_dir_no_dot_or_target(".") { let e = expect_action(e, ErrAction::Read, "."); if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { @@ -227,110 +209,38 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam println!("note: `cargo uibless` still needs to be run to update the test results"); } -fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec>) -> io::Result { - fn remove_lint(name: &str, lints: &mut Vec>) { - lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); - } - - fn remove_test_assets(name: &str) { - let test_file_stem = format!("tests/ui/{name}"); - let path = Path::new(&test_file_stem); - - // Some lints have their own directories, delete them - if path.is_dir() { - let _ = fs::remove_dir_all(path); - return; - } - - // Remove all related test files - let _ = fs::remove_file(path.with_extension("rs")); - let _ = fs::remove_file(path.with_extension("stderr")); - let _ = fs::remove_file(path.with_extension("fixed")); - } - - fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { - let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| { - content - .find("declare_lint_pass!") - .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`")) +/// Removes a lint's declaration and test files. Returns whether the module containing the +/// lint was deleted. +fn remove_lint_declaration(lint_idx: usize, lints: &mut Vec>, updater: &mut FileUpdater) -> bool { + let lint = lints.remove(lint_idx); + let delete_mod = if lints.iter().all(|l| l.module != lint.module) { + delete_file_if_exists(lint.path.as_ref()) + } else { + updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { + let mut start = &src[..lint.declaration_range.start]; + if start.ends_with("\n\n") { + start = &start[..start.len() - 1]; + } + let mut end = &src[lint.declaration_range.end..]; + if end.starts_with("\n\n") { + end = &end[1..]; + } + dst.push_str(start); + dst.push_str(end); + UpdateStatus::Changed }); - let mut impl_lint_pass_end = content[impl_lint_pass_start..] - .find(']') - .expect("failed to find `impl_lint_pass` terminator"); + false + }; + delete_test_files( + lint.name, + &lints[lint_idx..] + .iter() + .map(|l| l.name) + .take_while(|&n| n.starts_with(lint.name)) + .collect::>(), + ); - impl_lint_pass_end += impl_lint_pass_start; - if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(lint_name_upper) { - let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); - for c in content[lint_name_end..impl_lint_pass_end].chars() { - // Remove trailing whitespace - if c == ',' || c.is_whitespace() { - lint_name_end += 1; - } else { - break; - } - } - - content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, ""); - } - } - - if path.exists() - && let Some(lint) = lints.iter().find(|l| l.name == name) - { - if lint.module == name { - // The lint name is the same as the file, we can just delete the entire file - fs::remove_file(path)?; - } else { - // We can't delete the entire file, just remove the declaration - - if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { - // Remove clippy_lints/src/some_mod/some_lint.rs - let mut lint_mod_path = path.to_path_buf(); - lint_mod_path.set_file_name(name); - lint_mod_path.set_extension("rs"); - - let _ = fs::remove_file(lint_mod_path); - } - - let mut content = - fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); - - eprintln!( - "warn: you will have to manually remove any code related to `{name}` from `{}`", - path.display() - ); - - assert!( - content[lint.declaration_range].contains(&name.to_uppercase()), - "error: `{}` does not contain lint `{}`'s declaration", - path.display(), - lint.name - ); - - // Remove lint declaration (declare_clippy_lint!) - content.replace_range(lint.declaration_range, ""); - - // Remove the module declaration (mod xyz;) - let mod_decl = format!("\nmod {name};"); - content = content.replacen(&mod_decl, "", 1); - - remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); - fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); - } - - remove_test_assets(name); - remove_lint(name, lints); - return Ok(true); - } - - Ok(false) -} - -#[derive(Clone, Copy)] -enum ModEdit { - None, - Delete, - Rename, + delete_mod } fn collect_ui_test_names(lint: &str, ignored_prefixes: &[&str], dst: &mut Vec<(OsString, bool)>) { @@ -445,12 +355,50 @@ fn snake_to_pascal(s: &str) -> String { String::from_utf8(dst).unwrap() } -#[expect(clippy::too_many_lines)] -fn file_update_fn<'a, 'b>( +/// Creates an update function which replaces all instances of `clippy::old_name` with +/// `new_name`. +fn uplift_update_fn<'a>( old_name: &'a str, - new_name: &'b str, - mod_edit: ModEdit, -) -> impl use<'a, 'b> + FnMut(&Path, &str, &mut String) -> UpdateStatus { + new_name: &'a str, + remove_mod: bool, +) -> impl use<'a> + FnMut(&Path, &str, &mut String) -> UpdateStatus { + move |_, src, dst| { + let mut copy_pos = 0u32; + let mut changed = false; + let mut cursor = Cursor::new(src); + while let Some(ident) = cursor.find_any_ident() { + match cursor.get_text(ident) { + "mod" + if remove_mod && cursor.match_all(&[cursor::Pat::Ident(old_name), cursor::Pat::Semi], &mut []) => + { + dst.push_str(&src[copy_pos as usize..ident.pos as usize]); + dst.push_str(new_name); + copy_pos = cursor.pos(); + if src[copy_pos as usize..].starts_with('\n') { + copy_pos += 1; + } + changed = true; + }, + "clippy" if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::Ident(old_name)], &mut []) => { + dst.push_str(&src[copy_pos as usize..ident.pos as usize]); + dst.push_str(new_name); + copy_pos = cursor.pos(); + changed = true; + }, + + _ => {}, + } + } + dst.push_str(&src[copy_pos as usize..]); + UpdateStatus::from_changed(changed) + } +} + +fn rename_update_fn<'a>( + old_name: &'a str, + new_name: &'a str, + rename_mod: bool, +) -> impl use<'a> + FnMut(&Path, &str, &mut String) -> UpdateStatus { let old_name_pascal = snake_to_pascal(old_name); let new_name_pascal = snake_to_pascal(new_name); let old_name_upper = old_name.to_ascii_uppercase(); @@ -481,34 +429,15 @@ fn file_update_fn<'a, 'b>( }, // mod lint_name "mod" => { - if !matches!(mod_edit, ModEdit::None) - && let Some(pos) = cursor.find_ident(old_name) - { - match mod_edit { - ModEdit::Rename => { - dst.push_str(&src[copy_pos as usize..pos as usize]); - dst.push_str(new_name); - copy_pos = cursor.pos(); - changed = true; - }, - ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => { - let mut start = &src[copy_pos as usize..match_start as usize]; - if start.ends_with("\n\n") { - start = &start[..start.len() - 1]; - } - dst.push_str(start); - copy_pos = cursor.pos(); - if src[copy_pos as usize..].starts_with("\n\n") { - copy_pos += 1; - } - changed = true; - }, - ModEdit::Delete | ModEdit::None => {}, - } + if rename_mod && let Some(pos) = cursor.match_ident(old_name) { + dst.push_str(&src[copy_pos as usize..pos as usize]); + dst.push_str(new_name); + copy_pos = cursor.pos(); + changed = true; } }, // lint_name:: - name if matches!(mod_edit, ModEdit::Rename) && name == old_name => { + name if rename_mod && name == old_name => { let name_end = cursor.pos(); if cursor.match_pat(cursor::Pat::DoubleColon) { dst.push_str(&src[copy_pos as usize..match_start as usize]); diff --git a/clippy_dev/src/parse/cursor.rs b/clippy_dev/src/parse/cursor.rs index 6dc003f326de..2c142af4883a 100644 --- a/clippy_dev/src/parse/cursor.rs +++ b/clippy_dev/src/parse/cursor.rs @@ -219,6 +219,22 @@ impl<'txt> Cursor<'txt> { } } + /// Consume the returns the position of the next non-whitespace token if it's an + /// identifier. Returns `None` otherwise. + pub fn match_ident(&mut self, s: &str) -> Option { + loop { + match self.next_token.kind { + TokenKind::Ident if s == self.peek_text() => { + let pos = self.pos; + self.step(); + return Some(pos); + }, + TokenKind::Whitespace => self.step(), + _ => return None, + } + } + } + /// Continually attempt to match the pattern on subsequent tokens until a match is /// found. Returns whether the pattern was successfully matched. /// From 2e556c490b67a4ea09cb39285b753bfc06a13604 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 10 Jan 2026 15:31:52 +0100 Subject: [PATCH 069/273] fix(unnecessary_map_or): respect reduced applicability --- clippy_lints/src/methods/unnecessary_map_or.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs index 0c01be4b1875..7377b43571ce 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints/src/methods/unnecessary_map_or.rs @@ -97,12 +97,12 @@ pub(super) fn check<'a>( // being converted to `Some(5) == Some(5).then(|| 1)` isn't // the same thing + let mut app = Applicability::MachineApplicable; let inner_non_binding = Sugg::NonParen(Cow::Owned(format!( "{wrap}({})", - Sugg::hir(cx, non_binding_location, "") + Sugg::hir_with_applicability(cx, non_binding_location, "", &mut app) ))); - let mut app = Applicability::MachineApplicable; let binop = make_binop( op.node, &Sugg::hir_with_applicability(cx, recv, "..", &mut app), From 6fa61dfd995fee8ec2f766964899b55c6f304fa1 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 11 Jan 2026 09:06:44 +0100 Subject: [PATCH 070/273] Do not ignore statements before a `break` when simplifying loop The previous tests ignored statements in a block and only looked at the final expression to determine if the block was made of a single `break`. --- clippy_lints/src/loops/while_let_loop.rs | 30 ++++++++++++++---------- tests/ui/while_let_loop.rs | 21 +++++++++++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs index d4285db0abfc..e2ea24c39980 100644 --- a/clippy_lints/src/loops/while_let_loop.rs +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -40,7 +40,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_blo let_info, Some(if_let.if_then), ); - } else if els.and_then(|x| x.expr).is_some_and(is_simple_break_expr) + } else if els.is_some_and(is_simple_break_block) && let Some((pat, _)) = let_info { could_be_while_let(cx, expr, pat, init, has_trailing_exprs, let_info, None); @@ -61,17 +61,23 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_blo } } -/// Returns `true` if expr contains a single break expression without a label or sub-expression, -/// possibly embedded in blocks. -fn is_simple_break_expr(e: &Expr<'_>) -> bool { - if let ExprKind::Block(b, _) = e.kind { - match (b.stmts, b.expr) { - ([s], None) => matches!(s.kind, StmtKind::Expr(e) | StmtKind::Semi(e) if is_simple_break_expr(e)), - ([], Some(e)) => is_simple_break_expr(e), - _ => false, - } - } else { - matches!(e.kind, ExprKind::Break(dest, None) if dest.label.is_none()) +/// Checks if `block` contains a single unlabeled `break` expression or statement, possibly embedded +/// inside other blocks. +fn is_simple_break_block(block: &Block<'_>) -> bool { + match (block.stmts, block.expr) { + ([s], None) => matches!(s.kind, StmtKind::Expr(e) | StmtKind::Semi(e) if is_simple_break_expr(e)), + ([], Some(e)) => is_simple_break_expr(e), + _ => false, + } +} + +/// Checks if `expr` contains a single unlabeled `break` expression or statement, possibly embedded +/// inside other blocks. +fn is_simple_break_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Block(b, _) => is_simple_break_block(b), + ExprKind::Break(dest, None) => dest.label.is_none(), + _ => false, } } diff --git a/tests/ui/while_let_loop.rs b/tests/ui/while_let_loop.rs index f28c504742fd..b5a362669ce2 100644 --- a/tests/ui/while_let_loop.rs +++ b/tests/ui/while_let_loop.rs @@ -253,3 +253,24 @@ fn let_assign() { } } } + +fn issue16378() { + // This does not lint today because of the extra statement(s) + // before the `break`. + // TODO: When the `break` statement/expr in the `let`/`else` is the + // only way to leave the loop, the lint could trigger and move + // the statements preceeding the `break` after the loop, as in: + // ```rust + // while let Some(x) = std::hint::black_box(None::) { + // println!("x = {x}"); + // } + // println!("fail"); + // ``` + loop { + let Some(x) = std::hint::black_box(None::) else { + println!("fail"); + break; + }; + println!("x = {x}"); + } +} From 2b50456eb4b3b5abdb13a36b62a92a268b740d4e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sun, 23 Nov 2025 20:48:47 +0100 Subject: [PATCH 071/273] clean-up `unnecessary_map_or` and `manual_is_variant_and` - manual_is_variant_and: create `Flavor` using a function - removes one level of `for` --- .../src/methods/manual_is_variant_and.rs | 129 ++++++++++-------- clippy_lints/src/methods/mod.rs | 5 +- .../src/methods/unnecessary_map_or.rs | 34 ++--- tests/ui/unnecessary_map_or.fixed | 2 +- tests/ui/unnecessary_map_or.rs | 2 +- tests/ui/unnecessary_map_or.stderr | 38 +++--- 6 files changed, 111 insertions(+), 99 deletions(-) diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints/src/methods/manual_is_variant_and.rs index 8f65858987b9..5ce9d364cdd8 100644 --- a/clippy_lints/src/methods/manual_is_variant_and.rs +++ b/clippy_lints/src/methods/manual_is_variant_and.rs @@ -1,12 +1,13 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{get_parent_expr, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::def_id::DefId; use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -14,7 +15,37 @@ use rustc_span::{Span, Symbol}; use super::MANUAL_IS_VARIANT_AND; -pub(super) fn check( +#[derive(Clone, Copy, PartialEq)] +enum Flavor { + Option, + Result, +} + +impl Flavor { + fn new(cx: &LateContext<'_>, def_id: DefId) -> Option { + match cx.tcx.get_diagnostic_name(def_id)? { + sym::Option => Some(Self::Option), + sym::Result => Some(Self::Result), + _ => None, + } + } + + const fn diag_sym(self) -> Symbol { + match self { + Self::Option => sym::Option, + Self::Result => sym::Result, + } + } + + const fn positive_variant_name(self) -> Symbol { + match self { + Self::Option => sym::Some, + Self::Result => sym::Ok, + } + } +} + +pub(super) fn check_map_unwrap_or_default( cx: &LateContext<'_>, expr: &Expr<'_>, map_recv: &Expr<'_>, @@ -30,11 +61,13 @@ pub(super) fn check( } // 2. the caller of `map()` is neither `Option` nor `Result` - let is_option = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option); - let is_result = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Result); - if !is_option && !is_result { + let Some(flavor) = (cx.typeck_results()) + .expr_ty(map_recv) + .opt_def_id() + .and_then(|did| Flavor::new(cx, did)) + else { return; - } + }; // 3. the caller of `unwrap_or_default` is neither `Option` nor `Result` if !cx.typeck_results().expr_ty(expr).is_bool() { @@ -46,44 +79,23 @@ pub(super) fn check( return; } - let lint_msg = if is_option { - "called `map().unwrap_or_default()` on an `Option` value" - } else { - "called `map().unwrap_or_default()` on a `Result` value" + let lint_span = expr.span.with_lo(map_span.lo()); + let lint_msg = match flavor { + Flavor::Option => "called `map().unwrap_or_default()` on an `Option` value", + Flavor::Result => "called `map().unwrap_or_default()` on a `Result` value", }; - let suggestion = if is_option { "is_some_and" } else { "is_ok_and" }; - span_lint_and_sugg( - cx, - MANUAL_IS_VARIANT_AND, - expr.span.with_lo(map_span.lo()), - lint_msg, - "use", - format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")), - Applicability::MachineApplicable, - ); -} + span_lint_and_then(cx, MANUAL_IS_VARIANT_AND, lint_span, lint_msg, |diag| { + let method = match flavor { + Flavor::Option => "is_some_and", + Flavor::Result => "is_ok_and", + }; -#[derive(Clone, Copy, PartialEq)] -enum Flavor { - Option, - Result, -} + let mut app = Applicability::MachineApplicable; + let map_arg_snippet = snippet_with_applicability(cx, map_arg.span, "_", &mut app); -impl Flavor { - const fn symbol(self) -> Symbol { - match self { - Self::Option => sym::Option, - Self::Result => sym::Result, - } - } - - const fn positive(self) -> Symbol { - match self { - Self::Option => sym::Some, - Self::Result => sym::Ok, - } - } + diag.span_suggestion(lint_span, "use", format!("{method}({map_arg_snippet})"), app); + }); } #[derive(Clone, Copy, PartialEq)] @@ -178,7 +190,7 @@ fn emit_lint<'tcx>( cx, MANUAL_IS_VARIANT_AND, span, - format!("called `.map() {op} {pos}()`", pos = flavor.positive(),), + format!("called `.map() {op} {pos}()`", pos = flavor.positive_variant_name()), "use", format!( "{inversion}{recv}.{method}({body})", @@ -195,24 +207,23 @@ pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) { && op.span.eq_ctxt(expr.span) && let Ok(op) = Op::try_from(op.node) { - // Check `left` and `right` expression in any order, and for `Option` and `Result` + // Check `left` and `right` expression in any order for (expr1, expr2) in [(left, right), (right, left)] { - for flavor in [Flavor::Option, Flavor::Result] { - if let ExprKind::Call(call, [arg]) = expr1.kind - && let ExprKind::Lit(lit) = arg.kind - && let LitKind::Bool(bool_cst) = lit.node - && let ExprKind::Path(QPath::Resolved(_, path)) = call.kind - && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res - && let ty = cx.typeck_results().expr_ty(expr1) - && let ty::Adt(adt, args) = ty.kind() - && cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did()) - && args.type_at(0).is_bool() - && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind - && cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.symbol()) - && let Ok(map_func) = MapFunc::try_from(map_expr) - { - return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); - } + if let ExprKind::Call(call, [arg]) = expr1.kind + && let ExprKind::Lit(lit) = arg.kind + && let LitKind::Bool(bool_cst) = lit.node + && let ExprKind::Path(QPath::Resolved(_, path)) = call.kind + && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res + && let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind + && let ty = cx.typeck_results().expr_ty(expr1) + && let ty::Adt(adt, args) = ty.kind() + && let Some(flavor) = Flavor::new(cx, adt.did()) + && args.type_at(0).is_bool() + && cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.diag_sym()) + && let Ok(map_func) = MapFunc::try_from(map_expr) + { + emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv); + return; } } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index ee0cd95a5021..659a704a1ecb 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3933,7 +3933,8 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. + /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where `f` is a function or closure that returns the `bool` type. + /// /// Also checks for equality comparisons like `option.map(f) == Some(true)` and `result.map(f) == Ok(true)`. /// /// ### Why is this bad? @@ -5629,7 +5630,7 @@ impl Methods { manual_saturating_arithmetic::check_sub_unwrap_or_default(cx, expr, lhs, rhs); }, Some((sym::map, m_recv, [arg], span, _)) => { - manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv); + manual_is_variant_and::check_map_unwrap_or_default(cx, expr, m_recv, arg, span, self.msrv); }, Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { obfuscated_if_else::check( diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs index 7377b43571ce..21e112360aaf 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints/src/methods/unnecessary_map_or.rs @@ -8,7 +8,7 @@ use clippy_utils::sugg::{Sugg, make_binop}; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::visitors::is_local_used; use clippy_utils::{get_parent_expr, is_from_proc_macro}; -use rustc_ast::LitKind::Bool; +use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; @@ -48,13 +48,14 @@ pub(super) fn check<'a>( let ExprKind::Lit(def_kind) = def.kind else { return; }; - - let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); - - let Bool(def_bool) = def_kind.node else { + let LitKind::Bool(def_bool) = def_kind.node else { return; }; + let typeck = cx.typeck_results(); + + let recv_ty = typeck.expr_ty_adjusted(recv); + let variant = match recv_ty.opt_diag_name(cx) { Some(sym::Option) => Variant::Some, Some(sym::Result) => Variant::Ok, @@ -63,12 +64,12 @@ pub(super) fn check<'a>( let ext_def_span = def.span.until(map.span); - let (sugg, method, applicability) = if cx.typeck_results().expr_adjustments(recv).is_empty() + let (sugg, method, applicability): (_, Cow<'_, _>, _) = if typeck.expr_adjustments(recv).is_empty() && let ExprKind::Closure(map_closure) = map.kind && let closure_body = cx.tcx.hir_body(map_closure.body) && let closure_body_value = closure_body.value.peel_blocks() && let ExprKind::Binary(op, l, r) = closure_body_value.kind - && let Some(param) = closure_body.params.first() + && let [param] = closure_body.params && let PatKind::Binding(_, hir_id, _, _) = param.pat.kind // checking that map_or is one of the following: // .map_or(false, |x| x == y) @@ -78,14 +79,13 @@ pub(super) fn check<'a>( && ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool)) && let non_binding_location = if l.res_local_id() == Some(hir_id) { r } else { l } && switch_to_eager_eval(cx, non_binding_location) - // if its both then that's a strange edge case and + // if it's both then that's a strange edge case and // we can just ignore it, since by default clippy will error on this && (l.res_local_id() == Some(hir_id)) != (r.res_local_id() == Some(hir_id)) && !is_local_used(cx, non_binding_location, hir_id) - && let typeck_results = cx.typeck_results() - && let l_ty = typeck_results.expr_ty(l) - && l_ty == typeck_results.expr_ty(r) - && let Some(partial_eq) = cx.tcx.get_diagnostic_item(sym::PartialEq) + && let l_ty = typeck.expr_ty(l) + && l_ty == typeck.expr_ty(r) + && let Some(partial_eq) = cx.tcx.lang_items().eq_trait() && implements_trait(cx, recv_ty, partial_eq, &[recv_ty.into()]) && is_copy(cx, l_ty) { @@ -126,18 +126,18 @@ pub(super) fn check<'a>( } .into_string(); - (vec![(expr.span, sugg)], "a standard comparison", app) + (vec![(expr.span, sugg)], "a standard comparison".into(), app) } else if !def_bool && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) { let suggested_name = variant.method_name(); ( - vec![(method_span, suggested_name.into()), (ext_def_span, String::default())], - suggested_name, + vec![(method_span, suggested_name.into()), (ext_def_span, String::new())], + format!("`{suggested_name}`").into(), Applicability::MachineApplicable, ) } else if def_bool && matches!(variant, Variant::Some) && msrv.meets(cx, msrvs::IS_NONE_OR) { ( - vec![(method_span, "is_none_or".into()), (ext_def_span, String::default())], - "is_none_or", + vec![(method_span, "is_none_or".into()), (ext_def_span, String::new())], + "`is_none_or`".into(), Applicability::MachineApplicable, ) } else { diff --git a/tests/ui/unnecessary_map_or.fixed b/tests/ui/unnecessary_map_or.fixed index 10552431d65d..52c114339292 100644 --- a/tests/ui/unnecessary_map_or.fixed +++ b/tests/ui/unnecessary_map_or.fixed @@ -70,7 +70,7 @@ fn main() { let _ = r.is_ok_and(|x| x == 7); //~^ unnecessary_map_or - // lint constructs that are not comparaisons as well + // lint constructs that are not comparisons as well let func = |_x| true; let r: Result = Ok(3); let _ = r.is_ok_and(func); diff --git a/tests/ui/unnecessary_map_or.rs b/tests/ui/unnecessary_map_or.rs index 4b406ec2998b..dd2e1a569469 100644 --- a/tests/ui/unnecessary_map_or.rs +++ b/tests/ui/unnecessary_map_or.rs @@ -74,7 +74,7 @@ fn main() { let _ = r.map_or(false, |x| x == 7); //~^ unnecessary_map_or - // lint constructs that are not comparaisons as well + // lint constructs that are not comparisons as well let func = |_x| true; let r: Result = Ok(3); let _ = r.map_or(false, func); diff --git a/tests/ui/unnecessary_map_or.stderr b/tests/ui/unnecessary_map_or.stderr index b8a22346c378..d11e7179f921 100644 --- a/tests/ui/unnecessary_map_or.stderr +++ b/tests/ui/unnecessary_map_or.stderr @@ -56,7 +56,7 @@ LL | | 6 >= 5 LL | | }); | |______^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let _ = Some(5).map_or(false, |n| { LL + let _ = Some(5).is_some_and(|n| { @@ -68,7 +68,7 @@ error: this `map_or` can be simplified LL | let _ = Some(vec![5]).map_or(false, |n| n == [5]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let _ = Some(vec![5]).map_or(false, |n| n == [5]); LL + let _ = Some(vec![5]).is_some_and(|n| n == [5]); @@ -80,7 +80,7 @@ error: this `map_or` can be simplified LL | let _ = Some(vec![1]).map_or(false, |n| vec![2] == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let _ = Some(vec![1]).map_or(false, |n| vec![2] == n); LL + let _ = Some(vec![1]).is_some_and(|n| vec![2] == n); @@ -92,7 +92,7 @@ error: this `map_or` can be simplified LL | let _ = Some(5).map_or(false, |n| n == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let _ = Some(5).map_or(false, |n| n == n); LL + let _ = Some(5).is_some_and(|n| n == n); @@ -104,7 +104,7 @@ error: this `map_or` can be simplified LL | let _ = Some(5).map_or(false, |n| n == if 2 > 1 { n } else { 0 }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let _ = Some(5).map_or(false, |n| n == if 2 > 1 { n } else { 0 }); LL + let _ = Some(5).is_some_and(|n| n == if 2 > 1 { n } else { 0 }); @@ -116,7 +116,7 @@ error: this `map_or` can be simplified LL | let _ = Ok::, i32>(vec![5]).map_or(false, |n| n == [5]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_ok_and instead +help: use `is_ok_and` instead | LL - let _ = Ok::, i32>(vec![5]).map_or(false, |n| n == [5]); LL + let _ = Ok::, i32>(vec![5]).is_ok_and(|n| n == [5]); @@ -152,7 +152,7 @@ error: this `map_or` can be simplified LL | let _ = Some(5).map_or(true, |n| n == 5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - let _ = Some(5).map_or(true, |n| n == 5); LL + let _ = Some(5).is_none_or(|n| n == 5); @@ -164,7 +164,7 @@ error: this `map_or` can be simplified LL | let _ = Some(5).map_or(true, |n| 5 == n); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - let _ = Some(5).map_or(true, |n| 5 == n); LL + let _ = Some(5).is_none_or(|n| 5 == n); @@ -212,7 +212,7 @@ error: this `map_or` can be simplified LL | let _ = r.map_or(false, |x| x == 7); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_ok_and instead +help: use `is_ok_and` instead | LL - let _ = r.map_or(false, |x| x == 7); LL + let _ = r.is_ok_and(|x| x == 7); @@ -224,7 +224,7 @@ error: this `map_or` can be simplified LL | let _ = r.map_or(false, func); | ^^^^^^^^^^^^^^^^^^^^^ | -help: use is_ok_and instead +help: use `is_ok_and` instead | LL - let _ = r.map_or(false, func); LL + let _ = r.is_ok_and(func); @@ -236,7 +236,7 @@ error: this `map_or` can be simplified LL | let _ = Some(5).map_or(false, func); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let _ = Some(5).map_or(false, func); LL + let _ = Some(5).is_some_and(func); @@ -248,7 +248,7 @@ error: this `map_or` can be simplified LL | let _ = Some(5).map_or(true, func); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - let _ = Some(5).map_or(true, func); LL + let _ = Some(5).is_none_or(func); @@ -272,7 +272,7 @@ error: this `map_or` can be simplified LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) LL + o.is_none_or(|n| n > 5) || (o as &Option).map_or(true, |n| n < 5) @@ -284,7 +284,7 @@ error: this `map_or` can be simplified LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) LL + o.map_or(true, |n| n > 5) || (o as &Option).is_none_or(|n| n < 5) @@ -296,7 +296,7 @@ error: this `map_or` can be simplified LL | o.map_or(true, |n| n > 5) | ^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - o.map_or(true, |n| n > 5) LL + o.is_none_or(|n| n > 5) @@ -308,7 +308,7 @@ error: this `map_or` can be simplified LL | let x = a.map_or(false, |a| a == *s); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - let x = a.map_or(false, |a| a == *s); LL + let x = a.is_some_and(|a| a == *s); @@ -320,7 +320,7 @@ error: this `map_or` can be simplified LL | let y = b.map_or(true, |b| b == *s); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_none_or instead +help: use `is_none_or` instead | LL - let y = b.map_or(true, |b| b == *s); LL + let y = b.is_none_or(|b| b == *s); @@ -356,7 +356,7 @@ error: this `map_or` can be simplified LL | _ = s.lock().unwrap().map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - _ = s.lock().unwrap().map_or(false, |s| s == "foo"); LL + _ = s.lock().unwrap().is_some_and(|s| s == "foo"); @@ -368,7 +368,7 @@ error: this `map_or` can be simplified LL | _ = s.map_or(false, |s| s == "foo"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use is_some_and instead +help: use `is_some_and` instead | LL - _ = s.map_or(false, |s| s == "foo"); LL + _ = s.is_some_and(|s| s == "foo"); From d2e8aaa42fd3a2107fecbfc700863469909fc996 Mon Sep 17 00:00:00 2001 From: joboet Date: Mon, 12 Jan 2026 17:16:49 +0100 Subject: [PATCH 072/273] std: use `ByteStr`'s `Display` for `OsStr` --- library/std/src/sys/os_str/bytes.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs index 7ba6c46eaef4..5482663ef007 100644 --- a/library/std/src/sys/os_str/bytes.rs +++ b/library/std/src/sys/os_str/bytes.rs @@ -4,8 +4,8 @@ use core::clone::CloneToUninit; use crate::borrow::Cow; +use crate::bstr::ByteStr; use crate::collections::TryReserveError; -use crate::fmt::Write; use crate::rc::Rc; use crate::sync::Arc; use crate::sys::{AsInner, FromInner, IntoInner}; @@ -64,25 +64,7 @@ impl fmt::Debug for Slice { impl fmt::Display for Slice { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // If we're the empty string then our iterator won't actually yield - // anything, so perform the formatting manually - if self.inner.is_empty() { - return "".fmt(f); - } - - for chunk in self.inner.utf8_chunks() { - let valid = chunk.valid(); - // If we successfully decoded the whole chunk as a valid string then - // we can return a direct formatting of the string which will also - // respect various formatting flags if possible. - if chunk.invalid().is_empty() { - return valid.fmt(f); - } - - f.write_str(valid)?; - f.write_char(char::REPLACEMENT_CHARACTER)?; - } - Ok(()) + fmt::Display::fmt(ByteStr::new(&self.inner), f) } } From 3badf708c41402254349861d1a49cd3d9e128e99 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Mon, 8 Dec 2025 18:22:33 +0300 Subject: [PATCH 073/273] Mention an extra argument required to check tests Add a note to tests-related lint descriptions that clippy needs to be run with e.g. `--tests` argument to actually check tests, which is not quite obvious. --- clippy_lints/src/attrs/mod.rs | 4 ++++ clippy_lints/src/redundant_test_prefix.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 42c321df61c1..fa2951d91934 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -468,6 +468,10 @@ declare_clippy_lint! { /// #[ignore = "Some good reason"] /// fn test() {} /// ``` + /// + /// ### Note + /// Clippy can only lint compiled code. For this lint to trigger, you must configure `cargo clippy` + /// to include test compilation, for instance, by using flags such as `--tests` or `--all-targets`. #[clippy::version = "1.88.0"] pub IGNORE_WITHOUT_REASON, pedantic, diff --git a/clippy_lints/src/redundant_test_prefix.rs b/clippy_lints/src/redundant_test_prefix.rs index 84276e321657..602093259eae 100644 --- a/clippy_lints/src/redundant_test_prefix.rs +++ b/clippy_lints/src/redundant_test_prefix.rs @@ -47,6 +47,10 @@ declare_clippy_lint! { /// } /// } /// ``` + /// + /// ### Note + /// Clippy can only lint compiled code. For this lint to trigger, you must configure `cargo clippy` + /// to include test compilation, for instance, by using flags such as `--tests` or `--all-targets`. #[clippy::version = "1.88.0"] pub REDUNDANT_TEST_PREFIX, restriction, From 815407acb45eb42366995bf31b6ecfa9960af28f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 12 Jan 2026 15:41:02 +0100 Subject: [PATCH 074/273] clean-up early-return earlier, and create suggestion-related things later --- clippy_lints/src/strlen_on_c_strings.rs | 32 ++++++++++++------------- tests/ui/strlen_on_c_strings.fixed | 3 +-- tests/ui/strlen_on_c_strings.rs | 3 +-- tests/ui/strlen_on_c_strings.stderr | 28 +++++++++++----------- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index 0d50bd547652..2b3e0ce611ff 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::visitors::is_expr_unsafe; @@ -48,6 +48,15 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { && !recv.span.from_expansion() && path.ident.name == sym::as_ptr { + let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); + let method_name = if ty.is_diag_item(cx, sym::cstring_type) { + "as_bytes" + } else if ty.is_lang_item(cx, LangItem::CStr) { + "to_bytes" + } else { + return; + }; + let ctxt = expr.span.ctxt(); let span = match cx.tcx.parent_hir_node(expr.hir_id) { Node::Block(&Block { @@ -58,25 +67,16 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { _ => expr.span, }; - let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); - let mut app = Applicability::MachineApplicable; - let val_name = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0; - let method_name = if ty.is_diag_item(cx, sym::cstring_type) { - "as_bytes" - } else if ty.is_lang_item(cx, LangItem::CStr) { - "to_bytes" - } else { - return; - }; - - span_lint_and_sugg( + span_lint_and_then( cx, STRLEN_ON_C_STRINGS, span, "using `libc::strlen` on a `CString` or `CStr` value", - "try", - format!("{val_name}.{method_name}().len()"), - app, + |diag| { + let mut app = Applicability::MachineApplicable; + let val_name = snippet_with_context(cx, self_arg.span, ctxt, "_", &mut app).0; + diag.span_suggestion(span, "use", format!("{val_name}.{method_name}().len()"), app); + }, ); } } diff --git a/tests/ui/strlen_on_c_strings.fixed b/tests/ui/strlen_on_c_strings.fixed index 17c1b541f77c..f52c571c1088 100644 --- a/tests/ui/strlen_on_c_strings.fixed +++ b/tests/ui/strlen_on_c_strings.fixed @@ -1,7 +1,6 @@ #![warn(clippy::strlen_on_c_strings)] -#![allow(dead_code, clippy::manual_c_str_literals)] +#![allow(clippy::manual_c_str_literals)] -#[allow(unused)] use libc::strlen; use std::ffi::{CStr, CString}; diff --git a/tests/ui/strlen_on_c_strings.rs b/tests/ui/strlen_on_c_strings.rs index c641422f5df4..39366d08c1a2 100644 --- a/tests/ui/strlen_on_c_strings.rs +++ b/tests/ui/strlen_on_c_strings.rs @@ -1,7 +1,6 @@ #![warn(clippy::strlen_on_c_strings)] -#![allow(dead_code, clippy::manual_c_str_literals)] +#![allow(clippy::manual_c_str_literals)] -#[allow(unused)] use libc::strlen; use std::ffi::{CStr, CString}; diff --git a/tests/ui/strlen_on_c_strings.stderr b/tests/ui/strlen_on_c_strings.stderr index 84a93b99ee33..eecce8d97865 100644 --- a/tests/ui/strlen_on_c_strings.stderr +++ b/tests/ui/strlen_on_c_strings.stderr @@ -1,47 +1,47 @@ error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:11:13 + --> tests/ui/strlen_on_c_strings.rs:10:13 | LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cstring.as_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.as_bytes().len()` | = note: `-D clippy::strlen-on-c-strings` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::strlen_on_c_strings)]` error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:16:13 + --> tests/ui/strlen_on_c_strings.rs:15:13 | LL | let _ = unsafe { libc::strlen(cstr.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cstr.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:19:13 + --> tests/ui/strlen_on_c_strings.rs:18:13 | LL | let _ = unsafe { strlen(cstr.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `cstr.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:23:22 + --> tests/ui/strlen_on_c_strings.rs:22:22 | LL | let _ = unsafe { strlen((*pcstr).as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(*pcstr).to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `(*pcstr).to_bytes().len()` error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:29:22 + --> tests/ui/strlen_on_c_strings.rs:28:22 | LL | let _ = unsafe { strlen(unsafe_identity(cstr).as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unsafe_identity(cstr).to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe_identity(cstr).to_bytes().len()` error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:31:13 + --> tests/ui/strlen_on_c_strings.rs:30:13 | LL | let _ = unsafe { strlen(unsafe { unsafe_identity(cstr) }.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unsafe { unsafe_identity(cstr) }.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe { unsafe_identity(cstr) }.to_bytes().len()` error: using `libc::strlen` on a `CString` or `CStr` value - --> tests/ui/strlen_on_c_strings.rs:35:22 + --> tests/ui/strlen_on_c_strings.rs:34:22 | LL | let _ = unsafe { strlen(f(cstr).as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `f(cstr).to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `f(cstr).to_bytes().len()` error: aborting due to 7 previous errors From 7cdfee9de8d7d9c627c4b5be3c5a6e4a7c192d13 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 12 Jan 2026 15:44:20 +0100 Subject: [PATCH 075/273] feat: specify the type --- clippy_lints/src/strlen_on_c_strings.rs | 8 ++++---- tests/ui/strlen_on_c_strings.fixed | 14 +++++++------- tests/ui/strlen_on_c_strings.rs | 14 +++++++------- tests/ui/strlen_on_c_strings.stderr | 14 +++++++------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index 2b3e0ce611ff..ec0fc08ea552 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -49,10 +49,10 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { && path.ident.name == sym::as_ptr { let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); - let method_name = if ty.is_diag_item(cx, sym::cstring_type) { - "as_bytes" + let (ty_name, method_name) = if ty.is_diag_item(cx, sym::cstring_type) { + ("CString", "as_bytes") } else if ty.is_lang_item(cx, LangItem::CStr) { - "to_bytes" + ("CStr", "to_bytes") } else { return; }; @@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { cx, STRLEN_ON_C_STRINGS, span, - "using `libc::strlen` on a `CString` or `CStr` value", + format!("using `libc::strlen` on a `{ty_name}` value"), |diag| { let mut app = Applicability::MachineApplicable; let val_name = snippet_with_context(cx, self_arg.span, ctxt, "_", &mut app).0; diff --git a/tests/ui/strlen_on_c_strings.fixed b/tests/ui/strlen_on_c_strings.fixed index f52c571c1088..68cf1ba2edc5 100644 --- a/tests/ui/strlen_on_c_strings.fixed +++ b/tests/ui/strlen_on_c_strings.fixed @@ -8,29 +8,29 @@ fn main() { // CString let cstring = CString::new("foo").expect("CString::new failed"); let _ = cstring.as_bytes().len(); - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CString` value // CStr let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); let _ = cstr.to_bytes().len(); - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let _ = cstr.to_bytes().len(); - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let pcstr: *const &CStr = &cstr; let _ = unsafe { (*pcstr).to_bytes().len() }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value unsafe fn unsafe_identity(x: T) -> T { x } let _ = unsafe { unsafe_identity(cstr).to_bytes().len() }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let _ = unsafe { unsafe_identity(cstr) }.to_bytes().len(); - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let f: unsafe fn(_) -> _ = unsafe_identity; let _ = unsafe { f(cstr).to_bytes().len() }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value } diff --git a/tests/ui/strlen_on_c_strings.rs b/tests/ui/strlen_on_c_strings.rs index 39366d08c1a2..6d6eb0b5c152 100644 --- a/tests/ui/strlen_on_c_strings.rs +++ b/tests/ui/strlen_on_c_strings.rs @@ -8,29 +8,29 @@ fn main() { // CString let cstring = CString::new("foo").expect("CString::new failed"); let _ = unsafe { libc::strlen(cstring.as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CString` value // CStr let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); let _ = unsafe { libc::strlen(cstr.as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let _ = unsafe { strlen(cstr.as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let pcstr: *const &CStr = &cstr; let _ = unsafe { strlen((*pcstr).as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value unsafe fn unsafe_identity(x: T) -> T { x } let _ = unsafe { strlen(unsafe_identity(cstr).as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let _ = unsafe { strlen(unsafe { unsafe_identity(cstr) }.as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value let f: unsafe fn(_) -> _ = unsafe_identity; let _ = unsafe { strlen(f(cstr).as_ptr()) }; - //~^ strlen_on_c_strings + //~^ ERROR: using `libc::strlen` on a `CStr` value } diff --git a/tests/ui/strlen_on_c_strings.stderr b/tests/ui/strlen_on_c_strings.stderr index eecce8d97865..f2ccc91aabf1 100644 --- a/tests/ui/strlen_on_c_strings.stderr +++ b/tests/ui/strlen_on_c_strings.stderr @@ -1,4 +1,4 @@ -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CString` value --> tests/ui/strlen_on_c_strings.rs:10:13 | LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; @@ -7,37 +7,37 @@ LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; = note: `-D clippy::strlen-on-c-strings` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::strlen_on_c_strings)]` -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:15:13 | LL | let _ = unsafe { libc::strlen(cstr.as_ptr()) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:18:13 | LL | let _ = unsafe { strlen(cstr.as_ptr()) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:22:22 | LL | let _ = unsafe { strlen((*pcstr).as_ptr()) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `(*pcstr).to_bytes().len()` -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:28:22 | LL | let _ = unsafe { strlen(unsafe_identity(cstr).as_ptr()) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe_identity(cstr).to_bytes().len()` -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:30:13 | LL | let _ = unsafe { strlen(unsafe { unsafe_identity(cstr) }.as_ptr()) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe { unsafe_identity(cstr) }.to_bytes().len()` -error: using `libc::strlen` on a `CString` or `CStr` value +error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:34:22 | LL | let _ = unsafe { strlen(f(cstr).as_ptr()) }; From 49c86140fb2326ea58655c0fc3e46d52bcacce89 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Mon, 12 Jan 2026 17:54:50 +0100 Subject: [PATCH 076/273] feat: also lint types that dereference to `CStr` For simplicity's sake, this also changes the lint to always suggest `to_bytes`, as it is somewhat hard to find out whether a type could dereference to `CString` (which is what `as_bytes` would require) --- clippy_lints/src/strlen_on_c_strings.rs | 19 ++++++++++++------- tests/ui/strlen_on_c_strings.fixed | 14 ++++++++++++-- tests/ui/strlen_on_c_strings.rs | 12 +++++++++++- tests/ui/strlen_on_c_strings.stderr | 22 ++++++++++++++++++++-- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index ec0fc08ea552..5eb160720c52 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -47,14 +47,19 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { && let ExprKind::MethodCall(path, self_arg, [], _) = recv.kind && !recv.span.from_expansion() && path.ident.name == sym::as_ptr + && let typeck = cx.typeck_results() + && typeck + .expr_ty_adjusted(self_arg) + .peel_refs() + .is_lang_item(cx, LangItem::CStr) { - let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); - let (ty_name, method_name) = if ty.is_diag_item(cx, sym::cstring_type) { - ("CString", "as_bytes") + let ty = typeck.expr_ty(self_arg).peel_refs(); + let ty_kind = if ty.is_diag_item(cx, sym::cstring_type) { + "`CString` value" } else if ty.is_lang_item(cx, LangItem::CStr) { - ("CStr", "to_bytes") + "`CStr` value" } else { - return; + "type that dereferences to `CStr`" }; let ctxt = expr.span.ctxt(); @@ -71,11 +76,11 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { cx, STRLEN_ON_C_STRINGS, span, - format!("using `libc::strlen` on a `{ty_name}` value"), + format!("using `libc::strlen` on a {ty_kind}"), |diag| { let mut app = Applicability::MachineApplicable; let val_name = snippet_with_context(cx, self_arg.span, ctxt, "_", &mut app).0; - diag.span_suggestion(span, "use", format!("{val_name}.{method_name}().len()"), app); + diag.span_suggestion(span, "use", format!("{val_name}.to_bytes().len()"), app); }, ); } diff --git a/tests/ui/strlen_on_c_strings.fixed b/tests/ui/strlen_on_c_strings.fixed index 68cf1ba2edc5..33a328af6df4 100644 --- a/tests/ui/strlen_on_c_strings.fixed +++ b/tests/ui/strlen_on_c_strings.fixed @@ -1,5 +1,5 @@ #![warn(clippy::strlen_on_c_strings)] -#![allow(clippy::manual_c_str_literals)] +#![allow(clippy::manual_c_str_literals, clippy::boxed_local)] use libc::strlen; use std::ffi::{CStr, CString}; @@ -7,7 +7,7 @@ use std::ffi::{CStr, CString}; fn main() { // CString let cstring = CString::new("foo").expect("CString::new failed"); - let _ = cstring.as_bytes().len(); + let _ = cstring.to_bytes().len(); //~^ ERROR: using `libc::strlen` on a `CString` value // CStr @@ -34,3 +34,13 @@ fn main() { let _ = unsafe { f(cstr).to_bytes().len() }; //~^ ERROR: using `libc::strlen` on a `CStr` value } + +// make sure we lint types that _adjust_ to `CStr` +fn adjusted(box_cstring: Box, box_cstr: Box, arc_cstring: std::sync::Arc) { + let _ = box_cstring.to_bytes().len(); + //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` + let _ = box_cstr.to_bytes().len(); + //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` + let _ = arc_cstring.to_bytes().len(); + //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` +} diff --git a/tests/ui/strlen_on_c_strings.rs b/tests/ui/strlen_on_c_strings.rs index 6d6eb0b5c152..3c11c3a05269 100644 --- a/tests/ui/strlen_on_c_strings.rs +++ b/tests/ui/strlen_on_c_strings.rs @@ -1,5 +1,5 @@ #![warn(clippy::strlen_on_c_strings)] -#![allow(clippy::manual_c_str_literals)] +#![allow(clippy::manual_c_str_literals, clippy::boxed_local)] use libc::strlen; use std::ffi::{CStr, CString}; @@ -34,3 +34,13 @@ fn main() { let _ = unsafe { strlen(f(cstr).as_ptr()) }; //~^ ERROR: using `libc::strlen` on a `CStr` value } + +// make sure we lint types that _adjust_ to `CStr` +fn adjusted(box_cstring: Box, box_cstr: Box, arc_cstring: std::sync::Arc) { + let _ = unsafe { libc::strlen(box_cstring.as_ptr()) }; + //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` + let _ = unsafe { libc::strlen(box_cstr.as_ptr()) }; + //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` + let _ = unsafe { libc::strlen(arc_cstring.as_ptr()) }; + //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` +} diff --git a/tests/ui/strlen_on_c_strings.stderr b/tests/ui/strlen_on_c_strings.stderr index f2ccc91aabf1..2b059872a2da 100644 --- a/tests/ui/strlen_on_c_strings.stderr +++ b/tests/ui/strlen_on_c_strings.stderr @@ -2,7 +2,7 @@ error: using `libc::strlen` on a `CString` value --> tests/ui/strlen_on_c_strings.rs:10:13 | LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.as_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.to_bytes().len()` | = note: `-D clippy::strlen-on-c-strings` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::strlen_on_c_strings)]` @@ -43,5 +43,23 @@ error: using `libc::strlen` on a `CStr` value LL | let _ = unsafe { strlen(f(cstr).as_ptr()) }; | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `f(cstr).to_bytes().len()` -error: aborting due to 7 previous errors +error: using `libc::strlen` on a type that dereferences to `CStr` + --> tests/ui/strlen_on_c_strings.rs:40:13 + | +LL | let _ = unsafe { libc::strlen(box_cstring.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `box_cstring.to_bytes().len()` + +error: using `libc::strlen` on a type that dereferences to `CStr` + --> tests/ui/strlen_on_c_strings.rs:42:13 + | +LL | let _ = unsafe { libc::strlen(box_cstr.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `box_cstr.to_bytes().len()` + +error: using `libc::strlen` on a type that dereferences to `CStr` + --> tests/ui/strlen_on_c_strings.rs:44:13 + | +LL | let _ = unsafe { libc::strlen(arc_cstring.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `arc_cstring.to_bytes().len()` + +error: aborting due to 10 previous errors From 6c4cdfaf42bef13a33935835e09bedd353791af6 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Tue, 13 Jan 2026 23:11:42 +0100 Subject: [PATCH 077/273] =?UTF-8?q?Replace=20complex=20`matches!(=E2=80=A6?= =?UTF-8?q?,=20=E2=80=A6=20if=20=E2=80=A6)`=20by=20`if=20let`=20chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clippy_lints/src/slow_vector_initialization.rs | 4 +++- clippy_lints/src/unnested_or_patterns.rs | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index b25fa0905feb..8524497c387c 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -150,7 +150,9 @@ impl SlowVectorInit { && func.ty_rel_def(cx).is_diag_item(cx, sym::vec_with_capacity) { Some(InitializedSize::Initialized(len_expr)) - } else if matches!(expr.kind, ExprKind::Call(func, []) if func.ty_rel_def(cx).is_diag_item(cx, sym::vec_new)) { + } else if let ExprKind::Call(func, []) = expr.kind + && func.ty_rel_def(cx).is_diag_item(cx, sym::vec_new) + { Some(InitializedSize::Uninitialized) } else { None diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 975dd332ad06..12f8bd50e144 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -152,7 +152,12 @@ fn insert_necessary_parens(pat: &mut Pat) { walk_pat(self, pat); let target = match &mut pat.kind { // `i @ a | b`, `box a | b`, and `& mut? a | b`. - Ident(.., Some(p)) | Box(p) | Ref(p, _, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, + Ident(.., Some(p)) | Box(p) | Ref(p, _, _) + if let Or(ps) = &p.kind + && ps.len() > 1 => + { + p + }, // `&(mut x)` Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, _ => return, From b202eee0816938126195e95488ce6df8e9602214 Mon Sep 17 00:00:00 2001 From: Taeyoon Kim Date: Wed, 14 Jan 2026 10:06:39 +0900 Subject: [PATCH 078/273] Add Korean translation to Rust By Example Add korean translation. Thanks in advanced --- src/bootstrap/src/core/build_steps/doc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index b86582807f72..9bb16fe33fd5 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -75,7 +75,7 @@ book!( EditionGuide, "src/doc/edition-guide", "edition-guide", &[]; EmbeddedBook, "src/doc/embedded-book", "embedded-book", &[]; Nomicon, "src/doc/nomicon", "nomicon", &[]; - RustByExample, "src/doc/rust-by-example", "rust-by-example", &["es", "ja", "zh"]; + RustByExample, "src/doc/rust-by-example", "rust-by-example", &["es", "ja", "zh", "ko"]; RustdocBook, "src/doc/rustdoc", "rustdoc", &[]; StyleGuide, "src/doc/style-guide", "style-guide", &[]; ); From 795745ccf1f7af296334204931ae1d0e467d62c2 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Tue, 13 Jan 2026 19:48:38 -0500 Subject: [PATCH 079/273] remote-test-server: Fix compilation on UEFI targets Tested with: ./x build src/tools/remote-test-server --target x86_64-unknown-uefi --- src/tools/remote-test-server/src/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/remote-test-server/src/main.rs b/src/tools/remote-test-server/src/main.rs index bfe8f54937f6..a15f30d4659e 100644 --- a/src/tools/remote-test-server/src/main.rs +++ b/src/tools/remote-test-server/src/main.rs @@ -10,13 +10,13 @@ //! themselves having support libraries. All data over the TCP sockets is in a //! basically custom format suiting our needs. -#[cfg(all(not(windows), not(target_os = "motor")))] +#[cfg(not(any(windows, target_os = "motor", target_os = "uefi")))] use std::fs::Permissions; use std::fs::{self, File}; use std::io::prelude::*; use std::io::{self, BufReader}; use std::net::{SocketAddr, TcpListener, TcpStream}; -#[cfg(all(not(windows), not(target_os = "motor")))] +#[cfg(not(any(windows, target_os = "motor", target_os = "uefi")))] use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; @@ -325,7 +325,7 @@ fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, conf ])); } -#[cfg(all(not(windows), not(target_os = "motor")))] +#[cfg(not(any(windows, target_os = "motor", target_os = "uefi")))] fn get_status_code(status: &ExitStatus) -> (u8, i32) { match status.code() { Some(n) => (0, n), @@ -333,7 +333,7 @@ fn get_status_code(status: &ExitStatus) -> (u8, i32) { } } -#[cfg(any(windows, target_os = "motor"))] +#[cfg(any(windows, target_os = "motor", target_os = "uefi"))] fn get_status_code(status: &ExitStatus) -> (u8, i32) { (0, status.code().unwrap()) } @@ -359,11 +359,11 @@ fn recv(dir: &Path, io: &mut B) -> PathBuf { dst } -#[cfg(all(not(windows), not(target_os = "motor")))] +#[cfg(not(any(windows, target_os = "motor", target_os = "uefi")))] fn set_permissions(path: &Path) { t!(fs::set_permissions(&path, Permissions::from_mode(0o755))); } -#[cfg(any(windows, target_os = "motor"))] +#[cfg(any(windows, target_os = "motor", target_os = "uefi"))] fn set_permissions(_path: &Path) {} fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex) { From ed7479c658484647858e08342a9aa00aa0fb5b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 21:47:24 +0100 Subject: [PATCH 080/273] Add the GCC codegen backend to build-manifest --- src/tools/build-manifest/src/main.rs | 3 ++- src/tools/build-manifest/src/versions.rs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 4cec1b1f164b..7c0c528bcc04 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -32,7 +32,7 @@ static DOCS_FALLBACK: &[(&str, &str)] = &[ static PKG_INSTALLERS: &[&str] = &["x86_64-apple-darwin", "aarch64-apple-darwin"]; static NIGHTLY_ONLY_COMPONENTS: &[PkgType] = - &[PkgType::Miri, PkgType::JsonDocs, PkgType::RustcCodegenCranelift]; + &[PkgType::Miri, PkgType::JsonDocs, PkgType::RustcCodegenCranelift, PkgType::RustcCodegenGcc]; macro_rules! t { ($e:expr) => { @@ -302,6 +302,7 @@ impl Builder { | PkgType::RustAnalysis | PkgType::JsonDocs | PkgType::RustcCodegenCranelift + | PkgType::RustcCodegenGcc | PkgType::LlvmBitcodeLinker => { extensions.push(host_component(pkg)); } diff --git a/src/tools/build-manifest/src/versions.rs b/src/tools/build-manifest/src/versions.rs index 6ef8a0e83de3..1e55d1d467e6 100644 --- a/src/tools/build-manifest/src/versions.rs +++ b/src/tools/build-manifest/src/versions.rs @@ -59,6 +59,7 @@ pkg_type! { JsonDocs = "rust-docs-json"; preview = true, RustcCodegenCranelift = "rustc-codegen-cranelift"; preview = true, LlvmBitcodeLinker = "llvm-bitcode-linker"; preview = true, + RustcCodegenGcc = "rustc-codegen-gcc"; preview = true, } impl PkgType { @@ -82,6 +83,7 @@ impl PkgType { PkgType::LlvmTools => false, PkgType::Miri => false, PkgType::RustcCodegenCranelift => false, + PkgType::RustcCodegenGcc => false, PkgType::Rust => true, PkgType::RustStd => true, @@ -111,6 +113,7 @@ impl PkgType { RustcDocs => HOSTS, Cargo => HOSTS, RustcCodegenCranelift => HOSTS, + RustcCodegenGcc => HOSTS, RustMingw => MINGW, RustStd => TARGETS, HtmlDocs => HOSTS, From 4ceb13807e8062d5a4a3e9adecbd1d42a61eecf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 21:49:44 +0100 Subject: [PATCH 081/273] Forbid distributing GCC on CI if `gcc.download-ci-gcc` is enabled --- src/bootstrap/src/core/build_steps/dist.rs | 10 +++++++++- src/bootstrap/src/core/config/mod.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index cfcb144e0993..bfffc958f858 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -30,7 +30,7 @@ use crate::core::build_steps::tool::{ use crate::core::build_steps::vendor::{VENDOR_DIR, Vendor}; use crate::core::build_steps::{compile, llvm}; use crate::core::builder::{Builder, Kind, RunConfig, ShouldRun, Step, StepMetadata}; -use crate::core::config::TargetSelection; +use crate::core::config::{GccCiMode, TargetSelection}; use crate::utils::build_stamp::{self, BuildStamp}; use crate::utils::channel::{self, Info}; use crate::utils::exec::{BootstrapCommand, command}; @@ -3035,6 +3035,14 @@ impl Step for Gcc { return None; } + if builder.config.is_running_on_ci { + assert_eq!( + builder.config.gcc_ci_mode, + GccCiMode::BuildLocally, + "Cannot use gcc.download-ci-gcc when distributing GCC on CI" + ); + } + // We need the GCC sources to build GCC and also to add its license and README // files to the tarball builder.require_submodule( diff --git a/src/bootstrap/src/core/config/mod.rs b/src/bootstrap/src/core/config/mod.rs index 007ed4aaba13..7651def62672 100644 --- a/src/bootstrap/src/core/config/mod.rs +++ b/src/bootstrap/src/core/config/mod.rs @@ -425,7 +425,7 @@ impl std::str::FromStr for RustcLto { } /// Determines how will GCC be provided. -#[derive(Default, Clone)] +#[derive(Default, Debug, Clone, PartialEq)] pub enum GccCiMode { /// Build GCC from the local `src/gcc` submodule. BuildLocally, From d7e5996e4f55158af402fc77322812bea46eb8c2 Mon Sep 17 00:00:00 2001 From: Hugh Date: Wed, 14 Jan 2026 23:12:46 -0800 Subject: [PATCH 082/273] Skip elidable_lifetime_names lint for proc-macro generated code When linting code generated by proc macros (like `derivative`), the `elidable_lifetime_names` lint can produce fix suggestions with overlapping spans. This causes `cargo clippy --fix` to fail with "cannot replace slice of data that was already replaced". This change adds `is_from_proc_macro` checks to the three lint entry points (`check_item`, `check_impl_item`, `check_trait_item`) to skip linting proc-macro generated code entirely. Fixes #16316 --- clippy_lints/src/lifetimes.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 727e9b172a87..8917c90262a4 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::trait_ref_of_method; +use clippy_utils::{is_from_proc_macro, trait_ref_of_method}; use itertools::Itertools; use rustc_ast::visit::{try_visit, walk_list}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; @@ -149,16 +149,22 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { .. } = item.kind { + if is_from_proc_macro(cx, item) { + return; + } check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv); } else if let ItemKind::Impl(impl_) = &item.kind && !item.span.from_expansion() + && !is_from_proc_macro(cx, item) { report_extra_impl_lifetimes(cx, impl_); } } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { - if let ImplItemKind::Fn(ref sig, id) = item.kind { + if let ImplItemKind::Fn(ref sig, id) = item.kind + && !is_from_proc_macro(cx, item) + { let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id).is_none(); check_fn_inner( cx, @@ -174,7 +180,9 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(ref sig, ref body) = item.kind { + if let TraitItemKind::Fn(ref sig, ref body) = item.kind + && !is_from_proc_macro(cx, item) + { let (body, trait_sig) = match *body { TraitFn::Required(sig) => (None, Some(sig)), TraitFn::Provided(id) => (Some(id), None), From ea9b062a8a71d16f8327ddf7d9ceac405a9a3bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 15 Jan 2026 11:15:26 +0100 Subject: [PATCH 083/273] Add GCC to build-manifest --- src/tools/build-manifest/src/main.rs | 7 +-- src/tools/build-manifest/src/versions.rs | 56 +++++++++++++++++++----- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 7c0c528bcc04..cd6efad01141 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -158,7 +158,7 @@ impl Builder { } fn add_packages_to(&mut self, manifest: &mut Manifest) { - for pkg in PkgType::all() { + for pkg in &PkgType::all() { self.package(pkg, &mut manifest.pkg); } } @@ -227,7 +227,7 @@ impl Builder { }; for pkg in PkgType::all() { if pkg.is_preview() { - rename(pkg.tarball_component_name(), &pkg.manifest_component_name()); + rename(&pkg.tarball_component_name(), &pkg.manifest_component_name()); } } } @@ -263,7 +263,7 @@ impl Builder { let host_component = |pkg: &_| Component::from_pkg(pkg, host); - for pkg in PkgType::all() { + for pkg in &PkgType::all() { match pkg { // rustc/rust-std/cargo/docs are all required PkgType::Rustc | PkgType::Cargo | PkgType::HtmlDocs => { @@ -303,6 +303,7 @@ impl Builder { | PkgType::JsonDocs | PkgType::RustcCodegenCranelift | PkgType::RustcCodegenGcc + | PkgType::Gcc { .. } | PkgType::LlvmBitcodeLinker => { extensions.push(host_component(pkg)); } diff --git a/src/tools/build-manifest/src/versions.rs b/src/tools/build-manifest/src/versions.rs index 1e55d1d467e6..b53f6c5edc5d 100644 --- a/src/tools/build-manifest/src/versions.rs +++ b/src/tools/build-manifest/src/versions.rs @@ -11,29 +11,54 @@ use xz2::read::XzDecoder; const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu"; macro_rules! pkg_type { - ( $($variant:ident = $component:literal $(; preview = true $(@$is_preview:tt)? )? ),+ $(,)? ) => { + ( $($variant:ident = $component:literal $(; preview = true $(@$is_preview:tt)? )? $(; suffixes = [$($suffixes:literal),+] $(@$is_suffixed:tt)? )? ),+ $(,)? ) => { #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub(crate) enum PkgType { - $($variant,)+ + $($variant $( $($is_suffixed)? { suffix: &'static str })?,)+ } impl PkgType { pub(crate) fn is_preview(&self) -> bool { match self { - $( $( $($is_preview)? PkgType::$variant => true, )? )+ - _ => false, + $( PkgType::$variant $($($is_suffixed)? { .. })? => false $( $($is_preview)? || true)?, )+ } } - /// First part of the tarball name. - pub(crate) fn tarball_component_name(&self) -> &str { + /// First part of the tarball name. May include a suffix, if the package has one. + pub(crate) fn tarball_component_name(&self) -> String { match self { - $( PkgType::$variant => $component,)+ + $( PkgType::$variant $($($is_suffixed)? { suffix })? => { + #[allow(unused_mut)] + let mut name = $component.to_owned(); + $($($is_suffixed)? + name.push('-'); + name.push_str(suffix); + )? + name + },)+ } } - pub(crate) fn all() -> &'static [PkgType] { - &[ $(PkgType::$variant),+ ] + pub(crate) fn all() -> Vec { + let mut packages = vec![]; + $( + // Push the single variant + packages.push(PkgType::$variant $($($is_suffixed)? { suffix: "" })?); + // Macro hell, we have to remove the fake empty suffix if we actually have + // suffixes + $( + $($is_suffixed)? + packages.pop(); + )? + // And now add the suffixes, if any + $( + $($is_suffixed)? + $( + packages.push(PkgType::$variant { suffix: $suffixes }); + )+ + )? + )+ + packages } } } @@ -60,6 +85,9 @@ pkg_type! { RustcCodegenCranelift = "rustc-codegen-cranelift"; preview = true, LlvmBitcodeLinker = "llvm-bitcode-linker"; preview = true, RustcCodegenGcc = "rustc-codegen-gcc"; preview = true, + Gcc = "gcc"; preview = true; suffixes = [ + "x86_64-unknown-linux-gnu" + ], } impl PkgType { @@ -68,7 +96,7 @@ impl PkgType { if self.is_preview() { format!("{}-preview", self.tarball_component_name()) } else { - self.tarball_component_name().to_string() + self.tarball_component_name() } } @@ -84,6 +112,7 @@ impl PkgType { PkgType::Miri => false, PkgType::RustcCodegenCranelift => false, PkgType::RustcCodegenGcc => false, + PkgType::Gcc { suffix: _ } => false, PkgType::Rust => true, PkgType::RustStd => true, @@ -114,6 +143,13 @@ impl PkgType { Cargo => HOSTS, RustcCodegenCranelift => HOSTS, RustcCodegenGcc => HOSTS, + // Gcc is "special", because we need a separate libgccjit.so for each + // (host, target) compilation pair. So it's even more special than stdlib, which has a + // separate component per target. This component thus hardcodes its compilation + // target in its name, and we thus ship it for HOSTS only. So we essentially have + // gcc-T1, gcc-T2, a separate *component/package* per each compilation target. + // So on host T1, if you want to compile for T2, you would install gcc-T2. + Gcc { suffix: _ } => HOSTS, RustMingw => MINGW, RustStd => TARGETS, HtmlDocs => HOSTS, From 55abc484d7243eea77fd40247b531bcb0796e697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 15 Jan 2026 11:29:07 +0100 Subject: [PATCH 084/273] Extend build-manifest local test guide Fill in more blanks about how to test build-manifest changes with Rustup. --- src/tools/build-manifest/README.md | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/tools/build-manifest/README.md b/src/tools/build-manifest/README.md index bc1992ef80cc..f88949f48d8b 100644 --- a/src/tools/build-manifest/README.md +++ b/src/tools/build-manifest/README.md @@ -18,7 +18,7 @@ This gets called by `promote-release` build/dist/channel-rust-nightly.toml.sha256 +``` + +And start a HTTP server from the `build` directory: +```sh +cd build +python3 -m http.server 8000 +``` + +After you do all that, you can then install the locally generated components with rustup: +``` +rustup uninstall nightly +RUSTUP_DIST_SERVER=http://localhost:8000 rustup toolchain install nightly --profile minimal +RUSTUP_DIST_SERVER=http://localhost:8000 rustup +nightly component add +``` + +Note that generally it will not work to combine components built locally and those built from CI (nightly). Ideally, if you want to ship new rustup components, first dist them in nightly, and then test everything from nightly here after it's available on CI. From 3df0dc880376ef16076851046c40f2ad7374b63c Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sat, 10 Jan 2026 14:21:33 +0100 Subject: [PATCH 085/273] mark rust_dealloc as captures(address) Co-authored-by: Ralf Jung --- compiler/rustc_codegen_llvm/src/attributes.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index a25ce9e5a90a..bf6bb81b53b0 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -517,7 +517,16 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>( to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free)); // applies to argument place instead of function place let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx); - attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]); + let attrs: &[_] = if llvm_util::get_version() >= (21, 0, 0) { + // "Does not capture provenance" means "if the function call stashes the pointer somewhere, + // accessing that pointer after the function returns is UB". That is definitely the case here since + // freeing will destroy the provenance. + let captures_addr = AttributeKind::CapturesAddress.create_attr(cx.llcx); + &[allocated_pointer, captures_addr] + } else { + &[allocated_pointer] + }; + attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), attrs); } if let Some(align) = codegen_fn_attrs.alignment { llvm::set_alignment(llfn, align); From 676d71a45b4fa9966e77e9d4fe6508d8bbea7d29 Mon Sep 17 00:00:00 2001 From: Hugh Date: Thu, 15 Jan 2026 16:22:23 -0800 Subject: [PATCH 086/273] Defer is_from_proc_macro check and add tests Move the is_from_proc_macro check inside check_fn_inner to be called only right before emitting lints, rather than at the entry points. This avoids the expensive check when early returns would prevent any lint emission anyway. Add tests for proc-macro generated code covering all check locations: - Standalone functions - Methods in impl blocks - Trait methods - Impl blocks with extra lifetimes --- clippy_lints/src/lifetimes.rs | 46 +++++++++++++++++--------- tests/ui/extra_unused_lifetimes.rs | 32 ++++++++++++++++++ tests/ui/extra_unused_lifetimes.stderr | 14 ++++---- tests/ui/needless_lifetimes.fixed | 25 +++++++++++++- tests/ui/needless_lifetimes.rs | 25 +++++++++++++- 5 files changed, 118 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 8917c90262a4..679fb983d532 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -149,10 +149,9 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { .. } = item.kind { - if is_from_proc_macro(cx, item) { - return; - } - check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv); + check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv, || { + is_from_proc_macro(cx, item) + }); } else if let ItemKind::Impl(impl_) = &item.kind && !item.span.from_expansion() && !is_from_proc_macro(cx, item) @@ -162,9 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { - if let ImplItemKind::Fn(ref sig, id) = item.kind - && !is_from_proc_macro(cx, item) - { + if let ImplItemKind::Fn(ref sig, id) = item.kind { let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id).is_none(); check_fn_inner( cx, @@ -175,19 +172,28 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { item.span, report_extra_lifetimes, self.msrv, + || is_from_proc_macro(cx, item), ); } } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(ref sig, ref body) = item.kind - && !is_from_proc_macro(cx, item) - { + if let TraitItemKind::Fn(ref sig, ref body) = item.kind { let (body, trait_sig) = match *body { TraitFn::Required(sig) => (None, Some(sig)), TraitFn::Provided(id) => (Some(id), None), }; - check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true, self.msrv); + check_fn_inner( + cx, + sig, + body, + trait_sig, + item.generics, + item.span, + true, + self.msrv, + || is_from_proc_macro(cx, item), + ); } } } @@ -202,6 +208,7 @@ fn check_fn_inner<'tcx>( span: Span, report_extra_lifetimes: bool, msrv: Msrv, + is_from_proc_macro: impl FnOnce() -> bool, ) { if span.in_external_macro(cx.sess().source_map()) || has_where_lifetimes(cx, generics) { return; @@ -253,10 +260,19 @@ fn check_fn_inner<'tcx>( } } - if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv) { - if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) { - return; - } + let elidable = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv); + let has_elidable_lts = elidable + .as_ref() + .is_some_and(|(_, usages)| !usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span))); + + // Only check is_from_proc_macro if we're about to emit a lint (it's an expensive check) + if (has_elidable_lts || report_extra_lifetimes) && is_from_proc_macro() { + return; + } + + if let Some((elidable_lts, usages)) = elidable + && has_elidable_lts + { // async functions have usages whose spans point at the lifetime declaration which messes up // suggestions let include_suggestions = !sig.header.is_async(); diff --git a/tests/ui/extra_unused_lifetimes.rs b/tests/ui/extra_unused_lifetimes.rs index 5fdcd5a7e078..677523489599 100644 --- a/tests/ui/extra_unused_lifetimes.rs +++ b/tests/ui/extra_unused_lifetimes.rs @@ -1,4 +1,5 @@ //@aux-build:proc_macro_derive.rs +//@aux-build:proc_macros.rs #![allow( unused, @@ -11,6 +12,7 @@ #[macro_use] extern crate proc_macro_derive; +extern crate proc_macros; fn empty() {} @@ -148,4 +150,34 @@ mod issue_13578 { impl<'a, T: 'a> Foo for Option where &'a T: Foo {} } +// no lint on proc macro generated code +mod proc_macro_generated { + use proc_macros::external; + + // no lint on external macro (extra unused lifetimes in impl block) + external! { + struct ExternalImplStruct; + + impl<'a> ExternalImplStruct { + fn foo() {} + } + } + + // no lint on external macro (extra unused lifetimes in method) + external! { + struct ExternalMethodStruct; + + impl ExternalMethodStruct { + fn bar<'a>(&self) {} + } + } + + // no lint on external macro (extra unused lifetimes in trait method) + external! { + trait ExternalUnusedLifetimeTrait { + fn unused_lt<'a>(x: u8) {} + } + } +} + fn main() {} diff --git a/tests/ui/extra_unused_lifetimes.stderr b/tests/ui/extra_unused_lifetimes.stderr index 0cecbbe80f76..d748376d476c 100644 --- a/tests/ui/extra_unused_lifetimes.stderr +++ b/tests/ui/extra_unused_lifetimes.stderr @@ -1,5 +1,5 @@ error: this lifetime isn't used in the function definition - --> tests/ui/extra_unused_lifetimes.rs:19:14 + --> tests/ui/extra_unused_lifetimes.rs:21:14 | LL | fn unused_lt<'a>(x: u8) {} | ^^ @@ -8,37 +8,37 @@ LL | fn unused_lt<'a>(x: u8) {} = help: to override `-D warnings` add `#[allow(clippy::extra_unused_lifetimes)]` error: this lifetime isn't used in the function definition - --> tests/ui/extra_unused_lifetimes.rs:47:10 + --> tests/ui/extra_unused_lifetimes.rs:49:10 | LL | fn x<'a>(&self) {} | ^^ error: this lifetime isn't used in the function definition - --> tests/ui/extra_unused_lifetimes.rs:74:22 + --> tests/ui/extra_unused_lifetimes.rs:76:22 | LL | fn unused_lt<'a>(x: u8) {} | ^^ error: this lifetime isn't used in the impl - --> tests/ui/extra_unused_lifetimes.rs:86:10 + --> tests/ui/extra_unused_lifetimes.rs:88:10 | LL | impl<'a> std::ops::AddAssign<&Scalar> for &mut Scalar { | ^^ error: this lifetime isn't used in the impl - --> tests/ui/extra_unused_lifetimes.rs:93:10 + --> tests/ui/extra_unused_lifetimes.rs:95:10 | LL | impl<'b> Scalar { | ^^ error: this lifetime isn't used in the function definition - --> tests/ui/extra_unused_lifetimes.rs:95:26 + --> tests/ui/extra_unused_lifetimes.rs:97:26 | LL | pub fn something<'c>() -> Self { | ^^ error: this lifetime isn't used in the impl - --> tests/ui/extra_unused_lifetimes.rs:125:10 + --> tests/ui/extra_unused_lifetimes.rs:127:10 | LL | impl<'a, T: Source + ?Sized + 'a> Source for Box { | ^^ diff --git a/tests/ui/needless_lifetimes.fixed b/tests/ui/needless_lifetimes.fixed index 15ca409c95bd..90a07454b4d7 100644 --- a/tests/ui/needless_lifetimes.fixed +++ b/tests/ui/needless_lifetimes.fixed @@ -470,13 +470,36 @@ mod in_macro { } } - // no lint on external macro + // no lint on external macro (standalone function) external! { fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 { unimplemented!() } } + // no lint on external macro (method in impl block) + external! { + struct ExternalStruct; + + impl ExternalStruct { + fn needless_lifetime_method<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() + } + } + } + + // no lint on external macro (trait method) + external! { + trait ExternalTrait { + fn needless_lifetime_trait_method<'a>(x: &'a u8) -> &'a u8; + } + } + + // no lint on external macro (extra unused lifetimes in function) + external! { + fn extra_unused_lifetime<'a>(x: u8) {} + } + inline! { fn f<$'a>(arg: &$'a str) -> &$'a str { arg diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs index af9649d72987..6df38897f42d 100644 --- a/tests/ui/needless_lifetimes.rs +++ b/tests/ui/needless_lifetimes.rs @@ -470,13 +470,36 @@ mod in_macro { } } - // no lint on external macro + // no lint on external macro (standalone function) external! { fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 { unimplemented!() } } + // no lint on external macro (method in impl block) + external! { + struct ExternalStruct; + + impl ExternalStruct { + fn needless_lifetime_method<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() + } + } + } + + // no lint on external macro (trait method) + external! { + trait ExternalTrait { + fn needless_lifetime_trait_method<'a>(x: &'a u8) -> &'a u8; + } + } + + // no lint on external macro (extra unused lifetimes in function) + external! { + fn extra_unused_lifetime<'a>(x: u8) {} + } + inline! { fn f<$'a>(arg: &$'a str) -> &$'a str { arg From 19e0d7914f2865c707ace136fd33ab6f8c2931fb Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:08:09 +0000 Subject: [PATCH 087/273] Mention `cast_signed` in docs of `cast_possible_wrap` changelog: [`cast_possible_wrap`]: mention `cast_{un,}signed()` methods in doc I was evaluating this lint recently, and accepted using it because these methods exist. But the docs on the lint don't mention it, so I thought it would be prudent to include it in the docs. See also https://github.com/rust-lang/rust-clippy/pull/15384 Co-authored-by: Kaur Kuut Co-authored-by: Samuel Tardieu --- clippy_lints/src/casts/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 7220a8a80066..3c9ebef73f0d 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -145,6 +145,12 @@ declare_clippy_lint! { /// let _ = i32::try_from(u32::MAX).ok(); /// ``` /// + /// If the wrapping is intended, you can use: + /// ```no_run + /// let _ = u32::MAX.cast_signed(); + /// let _ = (-1i32).cast_unsigned(); + /// ``` + /// #[clippy::version = "pre 1.29.0"] pub CAST_POSSIBLE_WRAP, pedantic, From 2f3b9ce72c700766ad7e331b698afb38fd04f9b6 Mon Sep 17 00:00:00 2001 From: Dima Khort Date: Wed, 14 Jan 2026 00:31:54 +0100 Subject: [PATCH 088/273] feat(strlen_on_c_strings): suggest .count_bytes() --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/strlen_on_c_strings.rs | 31 +++++++++++++---- clippy_utils/src/msrvs.rs | 2 +- tests/ui/strlen_on_c_strings.fixed | 42 ++++++++++++++++------ tests/ui/strlen_on_c_strings.rs | 22 ++++++++++++ tests/ui/strlen_on_c_strings.stderr | 46 +++++++++++++++++++------ 6 files changed, 116 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ef2461f8b097..bd9db80d98ad 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -718,7 +718,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(|_| Box::::default()), Box::new(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))), Box::new(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))), - Box::new(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)), + Box::new(move |_| Box::new(strlen_on_c_strings::StrlenOnCStrings::new(conf))), Box::new(move |_| Box::new(self_named_constructors::SelfNamedConstructors)), Box::new(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)), Box::new(move |_| Box::new(manual_assert::ManualAssert)), diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index 5eb160720c52..962ab9cce14c 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -1,4 +1,6 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::visitors::is_expr_unsafe; @@ -6,12 +8,12 @@ use clippy_utils::{match_libc_symbol, sym}; use rustc_errors::Applicability; use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for usage of `libc::strlen` on a `CString` or `CStr` value, - /// and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead. + /// and suggest calling `count_bytes()` instead. /// /// ### Why is this bad? /// libc::strlen is an unsafe function, which we don't need to call @@ -27,15 +29,25 @@ declare_clippy_lint! { /// ```rust, no_run /// use std::ffi::CString; /// let cstring = CString::new("foo").expect("CString::new failed"); - /// let len = cstring.as_bytes().len(); + /// let len = cstring.count_bytes(); /// ``` #[clippy::version = "1.55.0"] pub STRLEN_ON_C_STRINGS, complexity, - "using `libc::strlen` on a `CString` or `CStr` value, while `as_bytes().len()` or `to_bytes().len()` respectively can be used instead" + "using `libc::strlen` on a `CString` or `CStr` value, while `count_bytes()` can be used instead" } -declare_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]); +pub struct StrlenOnCStrings { + msrv: Msrv, +} + +impl StrlenOnCStrings { + pub fn new(conf: &Conf) -> Self { + Self { msrv: conf.msrv } + } +} + +impl_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]); impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -80,7 +92,14 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings { |diag| { let mut app = Applicability::MachineApplicable; let val_name = snippet_with_context(cx, self_arg.span, ctxt, "_", &mut app).0; - diag.span_suggestion(span, "use", format!("{val_name}.to_bytes().len()"), app); + + let suggestion = if self.msrv.meets(cx, msrvs::CSTR_COUNT_BYTES) { + format!("{val_name}.count_bytes()") + } else { + format!("{val_name}.to_bytes().len()") + }; + + diag.span_suggestion(span, "use", suggestion, app); }, ); } diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 39a2c2df1f81..18fab6035f28 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -32,7 +32,7 @@ msrv_aliases! { 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS } 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF } 1,80,0 { BOX_INTO_ITER, LAZY_CELL } - 1,79,0 { CONST_BLOCKS } + 1,79,0 { CONST_BLOCKS, CSTR_COUNT_BYTES } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,75,0 { OPTION_AS_SLICE } diff --git a/tests/ui/strlen_on_c_strings.fixed b/tests/ui/strlen_on_c_strings.fixed index 33a328af6df4..6604da70874d 100644 --- a/tests/ui/strlen_on_c_strings.fixed +++ b/tests/ui/strlen_on_c_strings.fixed @@ -7,40 +7,62 @@ use std::ffi::{CStr, CString}; fn main() { // CString let cstring = CString::new("foo").expect("CString::new failed"); - let _ = cstring.to_bytes().len(); + let _ = cstring.count_bytes(); //~^ ERROR: using `libc::strlen` on a `CString` value // CStr let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); - let _ = cstr.to_bytes().len(); + let _ = cstr.count_bytes(); //~^ ERROR: using `libc::strlen` on a `CStr` value - let _ = cstr.to_bytes().len(); + let _ = cstr.count_bytes(); //~^ ERROR: using `libc::strlen` on a `CStr` value let pcstr: *const &CStr = &cstr; - let _ = unsafe { (*pcstr).to_bytes().len() }; + let _ = unsafe { (*pcstr).count_bytes() }; //~^ ERROR: using `libc::strlen` on a `CStr` value unsafe fn unsafe_identity(x: T) -> T { x } - let _ = unsafe { unsafe_identity(cstr).to_bytes().len() }; + let _ = unsafe { unsafe_identity(cstr).count_bytes() }; //~^ ERROR: using `libc::strlen` on a `CStr` value - let _ = unsafe { unsafe_identity(cstr) }.to_bytes().len(); + let _ = unsafe { unsafe_identity(cstr) }.count_bytes(); //~^ ERROR: using `libc::strlen` on a `CStr` value let f: unsafe fn(_) -> _ = unsafe_identity; - let _ = unsafe { f(cstr).to_bytes().len() }; + let _ = unsafe { f(cstr).count_bytes() }; //~^ ERROR: using `libc::strlen` on a `CStr` value } // make sure we lint types that _adjust_ to `CStr` fn adjusted(box_cstring: Box, box_cstr: Box, arc_cstring: std::sync::Arc) { - let _ = box_cstring.to_bytes().len(); + let _ = box_cstring.count_bytes(); //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` - let _ = box_cstr.to_bytes().len(); + let _ = box_cstr.count_bytes(); //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` - let _ = arc_cstring.to_bytes().len(); + let _ = arc_cstring.count_bytes(); //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` } + +#[clippy::msrv = "1.78"] +fn msrv_1_78() { + let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); + let _ = cstr.to_bytes().len(); + //~^ strlen_on_c_strings + + let cstring = CString::new("foo").expect("CString::new failed"); + let _ = cstring.to_bytes().len(); + //~^ strlen_on_c_strings +} + +#[clippy::msrv = "1.79"] +fn msrv_1_79() { + let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); + let _ = cstr.count_bytes(); + //~^ strlen_on_c_strings + + let cstring = CString::new("foo").expect("CString::new failed"); + let _ = cstring.count_bytes(); + //~^ strlen_on_c_strings +} diff --git a/tests/ui/strlen_on_c_strings.rs b/tests/ui/strlen_on_c_strings.rs index 3c11c3a05269..11fbdf585064 100644 --- a/tests/ui/strlen_on_c_strings.rs +++ b/tests/ui/strlen_on_c_strings.rs @@ -44,3 +44,25 @@ fn adjusted(box_cstring: Box, box_cstr: Box, arc_cstring: std::sy let _ = unsafe { libc::strlen(arc_cstring.as_ptr()) }; //~^ ERROR: using `libc::strlen` on a type that dereferences to `CStr` } + +#[clippy::msrv = "1.78"] +fn msrv_1_78() { + let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); + let _ = unsafe { libc::strlen(cstr.as_ptr()) }; + //~^ strlen_on_c_strings + + let cstring = CString::new("foo").expect("CString::new failed"); + let _ = unsafe { libc::strlen(cstring.as_ptr()) }; + //~^ strlen_on_c_strings +} + +#[clippy::msrv = "1.79"] +fn msrv_1_79() { + let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed"); + let _ = unsafe { libc::strlen(cstr.as_ptr()) }; + //~^ strlen_on_c_strings + + let cstring = CString::new("foo").expect("CString::new failed"); + let _ = unsafe { libc::strlen(cstring.as_ptr()) }; + //~^ strlen_on_c_strings +} diff --git a/tests/ui/strlen_on_c_strings.stderr b/tests/ui/strlen_on_c_strings.stderr index 2b059872a2da..1f1b5ccdb0ef 100644 --- a/tests/ui/strlen_on_c_strings.stderr +++ b/tests/ui/strlen_on_c_strings.stderr @@ -2,7 +2,7 @@ error: using `libc::strlen` on a `CString` value --> tests/ui/strlen_on_c_strings.rs:10:13 | LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.count_bytes()` | = note: `-D clippy::strlen-on-c-strings` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::strlen_on_c_strings)]` @@ -11,55 +11,79 @@ error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:15:13 | LL | let _ = unsafe { libc::strlen(cstr.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.count_bytes()` error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:18:13 | LL | let _ = unsafe { strlen(cstr.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.count_bytes()` error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:22:22 | LL | let _ = unsafe { strlen((*pcstr).as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `(*pcstr).to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `(*pcstr).count_bytes()` error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:28:22 | LL | let _ = unsafe { strlen(unsafe_identity(cstr).as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe_identity(cstr).to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe_identity(cstr).count_bytes()` error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:30:13 | LL | let _ = unsafe { strlen(unsafe { unsafe_identity(cstr) }.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe { unsafe_identity(cstr) }.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `unsafe { unsafe_identity(cstr) }.count_bytes()` error: using `libc::strlen` on a `CStr` value --> tests/ui/strlen_on_c_strings.rs:34:22 | LL | let _ = unsafe { strlen(f(cstr).as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `f(cstr).to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `f(cstr).count_bytes()` error: using `libc::strlen` on a type that dereferences to `CStr` --> tests/ui/strlen_on_c_strings.rs:40:13 | LL | let _ = unsafe { libc::strlen(box_cstring.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `box_cstring.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `box_cstring.count_bytes()` error: using `libc::strlen` on a type that dereferences to `CStr` --> tests/ui/strlen_on_c_strings.rs:42:13 | LL | let _ = unsafe { libc::strlen(box_cstr.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `box_cstr.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `box_cstr.count_bytes()` error: using `libc::strlen` on a type that dereferences to `CStr` --> tests/ui/strlen_on_c_strings.rs:44:13 | LL | let _ = unsafe { libc::strlen(arc_cstring.as_ptr()) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `arc_cstring.to_bytes().len()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `arc_cstring.count_bytes()` -error: aborting due to 10 previous errors +error: using `libc::strlen` on a `CStr` value + --> tests/ui/strlen_on_c_strings.rs:51:13 + | +LL | let _ = unsafe { libc::strlen(cstr.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.to_bytes().len()` + +error: using `libc::strlen` on a `CString` value + --> tests/ui/strlen_on_c_strings.rs:55:13 + | +LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.to_bytes().len()` + +error: using `libc::strlen` on a `CStr` value + --> tests/ui/strlen_on_c_strings.rs:62:13 + | +LL | let _ = unsafe { libc::strlen(cstr.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstr.count_bytes()` + +error: using `libc::strlen` on a `CString` value + --> tests/ui/strlen_on_c_strings.rs:66:13 + | +LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `cstring.count_bytes()` + +error: aborting due to 14 previous errors From 7d3bf37c4ddacf866129c90b58e0a4e7728368f2 Mon Sep 17 00:00:00 2001 From: Daedalus <16168171+RedDaedalus@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:02:03 -0700 Subject: [PATCH 089/273] fix fallback impl for select_unpredictable intrinsic --- library/core/src/intrinsics/mod.rs | 15 +++++++++------ .../intrinsics/select-unpredictable-drop.rs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 src/tools/miri/tests/pass/intrinsics/select-unpredictable-drop.rs diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 20f34036b25c..ac3456eb904e 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -55,7 +55,7 @@ #![allow(missing_docs)] use crate::ffi::va_list::{VaArgSafe, VaList}; -use crate::marker::{ConstParamTy, Destruct, DiscriminantKind, PointeeSized, Tuple}; +use crate::marker::{ConstParamTy, DiscriminantKind, PointeeSized, Tuple}; use crate::{mem, ptr}; mod bounds; @@ -482,11 +482,14 @@ pub const fn unlikely(b: bool) -> bool { #[rustc_nounwind] #[miri::intrinsic_fallback_is_spec] #[inline] -pub const fn select_unpredictable(b: bool, true_val: T, false_val: T) -> T -where - T: [const] Destruct, -{ - if b { true_val } else { false_val } +pub const fn select_unpredictable(b: bool, true_val: T, false_val: T) -> T { + if b { + forget(false_val); + true_val + } else { + forget(true_val); + false_val + } } /// A guard for unsafe functions that cannot ever be executed if `T` is uninhabited: diff --git a/src/tools/miri/tests/pass/intrinsics/select-unpredictable-drop.rs b/src/tools/miri/tests/pass/intrinsics/select-unpredictable-drop.rs new file mode 100644 index 000000000000..ecf9f4b92058 --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics/select-unpredictable-drop.rs @@ -0,0 +1,19 @@ +//! Check that `select_unpredictable` properly forgets the value it does not select. +#![feature(core_intrinsics)] +use std::cell::Cell; +use std::intrinsics::select_unpredictable; + +fn main() { + let (true_val, false_val) = (Cell::new(false), Cell::new(false)); + _ = select_unpredictable(true, TraceDrop(&true_val), TraceDrop(&false_val)); + assert!(true_val.get()); + assert!(!false_val.get()); +} + +struct TraceDrop<'a>(&'a Cell); + +impl<'a> Drop for TraceDrop<'a> { + fn drop(&mut self) { + self.0.set(true); + } +} From 4cb70c8e4a18c073703d3ccda8adeb2931fe4fc6 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Fri, 16 Jan 2026 03:46:38 +0000 Subject: [PATCH 090/273] fix: `unnecessary_sort_by` FN on field access --- .../src/methods/unnecessary_sort_by.rs | 24 ++++++++++----- tests/ui/unnecessary_sort_by.fixed | 30 +++++++++++++++++++ tests/ui/unnecessary_sort_by.rs | 30 +++++++++++++++++++ tests/ui/unnecessary_sort_by.stderr | 20 ++++++++++++- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index 9dddbe814317..c7339a3bdd05 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -2,12 +2,11 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::std_or_core; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::implements_trait; +use clippy_utils::ty::{implements_trait, is_copy}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_middle::ty::GenericArgKind; +use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_span::sym; use rustc_span::symbol::{Ident, Symbol}; use std::iter; @@ -70,7 +69,7 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: Ident, b_expr: &Expr<'_>, b_ident: mirrored_exprs(left_block, a_ident, right_block, b_ident) }, (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => { - left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, right_ident) + left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, b_ident) }, // Two paths: either one is a and the other is b, or they're identical to each other ( @@ -159,7 +158,11 @@ fn detect_lint<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, arg: &Expr<'_>) -> return Some(LintTrigger::Sort); } - if !expr_borrows(cx, left_expr) { + let left_expr_ty = cx.typeck_results().expr_ty(left_expr); + if !expr_borrows(left_expr_ty) + // Don't lint if the closure is accessing non-Copy fields + && (!expr_is_field_access(left_expr) || is_copy(cx, left_expr_ty)) + { return Some(LintTrigger::SortByKey(SortByKeyDetection { closure_arg, closure_body, @@ -171,11 +174,18 @@ fn detect_lint<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, arg: &Expr<'_>) -> None } -fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let ty = cx.typeck_results().expr_ty(expr); +fn expr_borrows(ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Ref(..)) || ty.walk().any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(_))) } +fn expr_is_field_access(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Field(_, _) => true, + ExprKind::AddrOf(_, Mutability::Not, inner) => expr_is_field_access(inner), + _ => false, + } +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, diff --git a/tests/ui/unnecessary_sort_by.fixed b/tests/ui/unnecessary_sort_by.fixed index 5255ab173efb..372cf683f223 100644 --- a/tests/ui/unnecessary_sort_by.fixed +++ b/tests/ui/unnecessary_sort_by.fixed @@ -111,3 +111,33 @@ fn main() { issue_5754::test(); issue_6001::test(); } + +fn issue16405() { + let mut v: Vec<(i32, &str)> = vec![(1, "foo"), (2, "bar")]; + + v.sort_by_key(|a| a.0); + //~^ unnecessary_sort_by + + struct Item { + key: i32, + value: String, + } + + let mut items = vec![ + Item { + key: 2, + value: "b".to_string(), + }, + Item { + key: 1, + value: "a".to_string(), + }, + ]; + items.sort_by_key(|item1| item1.key); + //~^ unnecessary_sort_by + + items.sort_by(|item1, item2| item1.value.cmp(&item2.value)); + + items.sort_by_key(|item1| item1.value.clone()); + //~^ unnecessary_sort_by +} diff --git a/tests/ui/unnecessary_sort_by.rs b/tests/ui/unnecessary_sort_by.rs index 65db7ca3f137..7dd274623b35 100644 --- a/tests/ui/unnecessary_sort_by.rs +++ b/tests/ui/unnecessary_sort_by.rs @@ -111,3 +111,33 @@ fn main() { issue_5754::test(); issue_6001::test(); } + +fn issue16405() { + let mut v: Vec<(i32, &str)> = vec![(1, "foo"), (2, "bar")]; + + v.sort_by(|a, b| a.0.cmp(&b.0)); + //~^ unnecessary_sort_by + + struct Item { + key: i32, + value: String, + } + + let mut items = vec![ + Item { + key: 2, + value: "b".to_string(), + }, + Item { + key: 1, + value: "a".to_string(), + }, + ]; + items.sort_by(|item1, item2| item1.key.cmp(&item2.key)); + //~^ unnecessary_sort_by + + items.sort_by(|item1, item2| item1.value.cmp(&item2.value)); + + items.sort_by(|item1, item2| item1.value.clone().cmp(&item2.value.clone())); + //~^ unnecessary_sort_by +} diff --git a/tests/ui/unnecessary_sort_by.stderr b/tests/ui/unnecessary_sort_by.stderr index a066554037fe..b555245b0d01 100644 --- a/tests/ui/unnecessary_sort_by.stderr +++ b/tests/ui/unnecessary_sort_by.stderr @@ -73,5 +73,23 @@ error: consider using `sort_unstable_by_key` LL | args.sort_unstable_by(|a, b| b.name().cmp(&a.name())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name()))` -error: aborting due to 12 previous errors +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:118:5 + | +LL | v.sort_by(|a, b| a.0.cmp(&b.0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|a| a.0)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:136:5 + | +LL | items.sort_by(|item1, item2| item1.key.cmp(&item2.key)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `items.sort_by_key(|item1| item1.key)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:141:5 + | +LL | items.sort_by(|item1, item2| item1.value.clone().cmp(&item2.value.clone())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `items.sort_by_key(|item1| item1.value.clone())` + +error: aborting due to 15 previous errors From 03fd408af5657404b1d165f7c7679918ed0f536a Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Fri, 16 Jan 2026 05:08:40 +0000 Subject: [PATCH 091/273] Apply `unnecessary_sort_by` to Clippy itself --- clippy_utils/src/ty/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index a90d64e972c1..979170d72006 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -824,7 +824,7 @@ impl AdtVariantInfo { } }) .collect::>(); - variants_size.sort_by(|a, b| b.size.cmp(&a.size)); + variants_size.sort_by_key(|b| std::cmp::Reverse(b.size)); variants_size } } From b7e4315b3287ba27cbaf396a0f3d7baf7c237ecd Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 15 Jan 2026 20:35:35 +0100 Subject: [PATCH 092/273] Do not output an error if standard output is full on --help/--version This matches rustc's behavior. --- src/driver.rs | 13 ++++++++----- src/main.rs | 17 +++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/driver.rs b/src/driver.rs index 8693973ef78c..6094c8445398 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -32,11 +32,10 @@ use rustc_span::symbol::Symbol; use std::env; use std::fs::read_to_string; +use std::io::Write as _; use std::path::Path; use std::process::exit; -use anstream::println; - /// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If /// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`. fn arg_value<'a>(args: &'a [String], find_arg: &str, pred: impl Fn(&str) -> bool) -> Option<&'a str> { @@ -186,7 +185,9 @@ impl rustc_driver::Callbacks for ClippyCallbacks { } fn display_help() { - println!("{}", help_message()); + if writeln!(&mut anstream::stdout().lock(), "{}", help_message()).is_err() { + exit(rustc_driver::EXIT_FAILURE); + } } const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml"; @@ -253,8 +254,10 @@ pub fn main() { if orig_args.iter().any(|a| a == "--version" || a == "-V") { let version_info = rustc_tools_util::get_version_info!(); - println!("{version_info}"); - exit(0); + match writeln!(&mut anstream::stdout().lock(), "{version_info}") { + Ok(()) => exit(rustc_driver::EXIT_SUCCESS), + Err(_) => exit(rustc_driver::EXIT_FAILURE), + } } // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument. diff --git a/src/main.rs b/src/main.rs index 688161c7bfcb..98b888444831 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,19 +4,24 @@ // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] -use std::env; -use std::path::PathBuf; -use std::process::{self, Command}; +extern crate rustc_driver; -use anstream::println; +use std::env; +use std::io::Write as _; +use std::path::PathBuf; +use std::process::{self, Command, exit}; fn show_help() { - println!("{}", help_message()); + if writeln!(&mut anstream::stdout().lock(), "{}", help_message()).is_err() { + exit(rustc_driver::EXIT_FAILURE); + } } fn show_version() { let version_info = rustc_tools_util::get_version_info!(); - println!("{version_info}"); + if writeln!(&mut anstream::stdout().lock(), "{version_info}").is_err() { + exit(rustc_driver::EXIT_FAILURE); + } } pub fn main() { From c9bbf951c22fff8a9c1bc4cfc4c04b43bd0fe431 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Fri, 16 Jan 2026 20:22:33 +0000 Subject: [PATCH 093/273] Allow `unnecessary_sort_by` to lint closures with input patterns --- .../src/methods/unnecessary_sort_by.rs | 288 +++++++++++++----- clippy_utils/src/ty/mod.rs | 15 + tests/ui/unnecessary_sort_by.fixed | 32 ++ tests/ui/unnecessary_sort_by.rs | 32 ++ tests/ui/unnecessary_sort_by.stderr | 44 ++- 5 files changed, 341 insertions(+), 70 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index c7339a3bdd05..ac13fc0b6850 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -1,42 +1,51 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::std_or_core; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{implements_trait, is_copy}; +use clippy_utils::ty::{implements_trait, is_copy, peel_n_ty_refs}; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; -use rustc_span::sym; -use rustc_span::symbol::{Ident, Symbol}; +use rustc_span::symbol::Ident; +use rustc_span::{Span, sym}; use std::iter; +use std::ops::Not; use super::UNNECESSARY_SORT_BY; -enum LintTrigger<'tcx> { +enum LintTrigger { Sort, - SortByKey(SortByKeyDetection<'tcx>), + SortByKey(SortByKeyDetection), } -struct SortByKeyDetection<'tcx> { - closure_arg: Symbol, - closure_body: &'tcx Expr<'tcx>, +struct SortByKeyDetection { + closure_arg: Span, + closure_body: String, reverse: bool, + applicability: Applicability, } /// Detect if the two expressions are mirrored (identical, except one /// contains a and the other replaces it with b) -fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: Ident, b_expr: &Expr<'_>, b_ident: Ident) -> bool { +fn mirrored_exprs( + a_expr: &Expr<'_>, + b_expr: &Expr<'_>, + binding_map: &BindingMap, + binding_source: BindingSource, +) -> bool { match (a_expr.kind, b_expr.kind) { // Two arrays with mirrored contents - (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => { - iter::zip(left_exprs, right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) - }, + (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => iter::zip(left_exprs, right_exprs) + .all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source)), // The two exprs are function calls. // Check to see that the function itself and its arguments are mirrored (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => { - mirrored_exprs(left_expr, a_ident, right_expr, b_ident) - && iter::zip(left_args, right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) + mirrored_exprs(left_expr, right_expr, binding_map, binding_source) + && iter::zip(left_args, right_args) + .all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source)) }, // The two exprs are method calls. // Check to see that the function is the same and the arguments and receivers are mirrored @@ -45,31 +54,31 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: Ident, b_expr: &Expr<'_>, b_ident: ExprKind::MethodCall(right_segment, right_receiver, right_args, _), ) => { left_segment.ident == right_segment.ident - && iter::zip(left_args, right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) - && mirrored_exprs(left_receiver, a_ident, right_receiver, b_ident) + && iter::zip(left_args, right_args) + .all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source)) + && mirrored_exprs(left_receiver, right_receiver, binding_map, binding_source) }, // Two tuples with mirrored contents - (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => { - iter::zip(left_exprs, right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) - }, + (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => iter::zip(left_exprs, right_exprs) + .all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source)), // Two binary ops, which are the same operation and which have mirrored arguments (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => { left_op.node == right_op.node - && mirrored_exprs(left_left, a_ident, right_left, b_ident) - && mirrored_exprs(left_right, a_ident, right_right, b_ident) + && mirrored_exprs(left_left, right_left, binding_map, binding_source) + && mirrored_exprs(left_right, right_right, binding_map, binding_source) }, // Two unary ops, which are the same operation and which have the same argument (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => { - left_op == right_op && mirrored_exprs(left_expr, a_ident, right_expr, b_ident) + left_op == right_op && mirrored_exprs(left_expr, right_expr, binding_map, binding_source) }, // The two exprs are literals of some kind (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node, - (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, a_ident, right, b_ident), + (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, right, binding_map, binding_source), (ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => { - mirrored_exprs(left_block, a_ident, right_block, b_ident) + mirrored_exprs(left_block, right_block, binding_map, binding_source) }, (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => { - left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, b_ident) + left_ident.name == right_ident.name && mirrored_exprs(left_expr, right_expr, binding_map, binding_source) }, // Two paths: either one is a and the other is b, or they're identical to each other ( @@ -89,60 +98,174 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: Ident, b_expr: &Expr<'_>, b_ident: )), ) => { (iter::zip(left_segments, right_segments).all(|(left, right)| left.ident == right.ident) - && left_segments - .iter() - .all(|seg| seg.ident != a_ident && seg.ident != b_ident)) + && left_segments.iter().all(|seg| { + !binding_map.contains_key(&BindingKey { + ident: seg.ident, + source: BindingSource::Left, + }) && !binding_map.contains_key(&BindingKey { + ident: seg.ident, + source: BindingSource::Right, + }) + })) || (left_segments.len() == 1 - && left_segments[0].ident == a_ident && right_segments.len() == 1 - && right_segments[0].ident == b_ident) + && binding_map + .get(&BindingKey { + ident: left_segments[0].ident, + source: binding_source, + }) + .is_some_and(|value| value.mirrored.ident == right_segments[0].ident)) }, // Matching expressions, but one or both is borrowed ( ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), ExprKind::AddrOf(right_kind, Mutability::Not, right_expr), - ) => left_kind == right_kind && mirrored_exprs(left_expr, a_ident, right_expr, b_ident), - (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => mirrored_exprs(a_expr, a_ident, right_expr, b_ident), - (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(left_expr, a_ident, b_expr, b_ident), + ) => left_kind == right_kind && mirrored_exprs(left_expr, right_expr, binding_map, binding_source), + (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => { + mirrored_exprs(a_expr, right_expr, binding_map, binding_source) + }, + (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => { + mirrored_exprs(left_expr, b_expr, binding_map, binding_source) + }, _ => false, } } -fn detect_lint<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, arg: &Expr<'_>) -> Option> { +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum BindingSource { + Left, + Right, +} + +impl Not for BindingSource { + type Output = BindingSource; + + fn not(self) -> Self::Output { + match self { + BindingSource::Left => BindingSource::Right, + BindingSource::Right => BindingSource::Left, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct BindingKey { + /// The identifier of the binding. + ident: Ident, + /// The source of the binding. + source: BindingSource, +} + +struct BindingValue { + /// The mirrored binding. + mirrored: BindingKey, + /// The number of refs the binding is wrapped in. + n_refs: usize, +} + +/// A map from binding info to the number of refs the binding is wrapped in. +type BindingMap = FxHashMap; +/// Extract the binding pairs, if the two patterns are mirrored. The pats are assumed to be used in +/// closure inputs and thus irrefutable. +fn mapping_of_mirrored_pats(a_pat: &Pat<'_>, b_pat: &Pat<'_>) -> Option { + fn mapping_of_mirrored_pats_inner( + a_pat: &Pat<'_>, + b_pat: &Pat<'_>, + mapping: &mut BindingMap, + n_refs: usize, + ) -> bool { + match (&a_pat.kind, &b_pat.kind) { + (PatKind::Tuple(a_pats, a_dots), PatKind::Tuple(b_pats, b_dots)) => { + a_dots == b_dots + && a_pats.len() == b_pats.len() + && iter::zip(a_pats.iter(), b_pats.iter()) + .all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs)) + }, + (PatKind::Binding(_, _, a_ident, _), PatKind::Binding(_, _, b_ident, _)) => { + let a_key = BindingKey { + ident: *a_ident, + source: BindingSource::Left, + }; + let b_key = BindingKey { + ident: *b_ident, + source: BindingSource::Right, + }; + let a_value = BindingValue { + mirrored: b_key, + n_refs, + }; + let b_value = BindingValue { + mirrored: a_key, + n_refs, + }; + mapping.insert(a_key, a_value); + mapping.insert(b_key, b_value); + true + }, + (PatKind::Wild, PatKind::Wild) => true, + (PatKind::TupleStruct(_, a_pats, a_dots), PatKind::TupleStruct(_, b_pats, b_dots)) => { + a_dots == b_dots + && a_pats.len() == b_pats.len() + && iter::zip(a_pats.iter(), b_pats.iter()) + .all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs)) + }, + (PatKind::Struct(_, a_fields, a_rest), PatKind::Struct(_, b_fields, b_rest)) => { + a_rest == b_rest + && a_fields.len() == b_fields.len() + && iter::zip(a_fields.iter(), b_fields.iter()).all(|(a_field, b_field)| { + a_field.ident == b_field.ident + && mapping_of_mirrored_pats_inner(a_field.pat, b_field.pat, mapping, n_refs) + }) + }, + (PatKind::Ref(a_inner, _, _), PatKind::Ref(b_inner, _, _)) => { + mapping_of_mirrored_pats_inner(a_inner, b_inner, mapping, n_refs + 1) + }, + (PatKind::Slice(a_elems, None, a_rest), PatKind::Slice(b_elems, None, b_rest)) => { + a_elems.len() == b_elems.len() + && iter::zip(a_elems.iter(), b_elems.iter()) + .all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs)) + && a_rest.len() == b_rest.len() + && iter::zip(a_rest.iter(), b_rest.iter()) + .all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs)) + }, + _ => false, + } + } + + let mut mapping = FxHashMap::default(); + if mapping_of_mirrored_pats_inner(a_pat, b_pat, &mut mapping, 0) { + return Some(mapping); + } + + None +} + +fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) -> Option { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() && let ExprKind::Closure(&Closure { body, .. }) = arg.kind && let closure_body = cx.tcx.hir_body(body) - && let &[ - Param { - pat: - &Pat { - kind: PatKind::Binding(_, _, left_ident, _), - .. - }, - .. - }, - Param { - pat: - &Pat { - kind: PatKind::Binding(_, _, right_ident, _), - .. - }, - .. - }, - ] = closure_body.params + && let &[Param { pat: l_pat, .. }, Param { pat: r_pat, .. }] = closure_body.params + && let Some(binding_map) = mapping_of_mirrored_pats(l_pat, r_pat) && let ExprKind::MethodCall(method_path, left_expr, [right_expr], _) = closure_body.value.kind && method_path.ident.name == sym::cmp && let Some(ord_trait) = cx.tcx.get_diagnostic_item(sym::Ord) && cx.ty_based_def(closure_body.value).opt_parent(cx).opt_def_id() == Some(ord_trait) { - let (closure_body, closure_arg, reverse) = if mirrored_exprs(left_expr, left_ident, right_expr, right_ident) { - (left_expr, left_ident.name, false) - } else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) { - (left_expr, right_ident.name, true) + let (closure_body, closure_arg, reverse) = + if mirrored_exprs(left_expr, right_expr, &binding_map, BindingSource::Left) { + (left_expr, l_pat.span, false) + } else if mirrored_exprs(left_expr, right_expr, &binding_map, BindingSource::Right) { + (left_expr, r_pat.span, true) + } else { + return None; + }; + + let mut applicability = if reverse { + Applicability::MaybeIncorrect } else { - return None; + Applicability::MachineApplicable }; if let ExprKind::Path(QPath::Resolved( @@ -152,10 +275,39 @@ fn detect_lint<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, arg: &Expr<'_>) -> .. }, )) = left_expr.kind - && *left_name == left_ident - && implements_trait(cx, cx.typeck_results().expr_ty(left_expr), ord_trait, &[]) { - return Some(LintTrigger::Sort); + if let PatKind::Binding(_, _, left_ident, _) = l_pat.kind + && *left_name == left_ident + && implements_trait(cx, cx.typeck_results().expr_ty(left_expr), ord_trait, &[]) + { + return Some(LintTrigger::Sort); + } + + let mut left_expr_ty = cx.typeck_results().expr_ty(left_expr); + let left_ident_n_refs = binding_map + .get(&BindingKey { + ident: *left_name, + source: BindingSource::Left, + }) + .map_or(0, |value| value.n_refs); + // Peel off the outer-most ref which is introduced by the closure, if it is not already peeled + // by the pattern + if left_ident_n_refs == 0 { + (left_expr_ty, _) = peel_n_ty_refs(left_expr_ty, 1); + } + if !reverse && is_copy(cx, left_expr_ty) { + let mut closure_body = + snippet_with_applicability(cx, closure_body.span, "_", &mut applicability).to_string(); + if left_ident_n_refs == 0 { + closure_body = format!("*{closure_body}"); + } + return Some(LintTrigger::SortByKey(SortByKeyDetection { + closure_arg, + closure_body, + reverse, + applicability, + })); + } } let left_expr_ty = cx.typeck_results().expr_ty(left_expr); @@ -163,10 +315,12 @@ fn detect_lint<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, arg: &Expr<'_>) -> // Don't lint if the closure is accessing non-Copy fields && (!expr_is_field_access(left_expr) || is_copy(cx, left_expr_ty)) { + let closure_body = Sugg::hir_with_applicability(cx, closure_body, "_", &mut applicability).to_string(); return Some(LintTrigger::SortByKey(SortByKeyDetection { closure_arg, closure_body, reverse, + applicability, })); } } @@ -212,22 +366,18 @@ pub(super) fn check<'tcx>( expr.span, format!("consider using `{method}`"), |diag| { - let mut app = if trigger.reverse { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; + let mut app = trigger.applicability; let recv = Sugg::hir_with_applicability(cx, recv, "(_)", &mut app); - let closure_body = Sugg::hir_with_applicability(cx, trigger.closure_body, "_", &mut app); let closure_body = if trigger.reverse { - format!("{std_or_core}::cmp::Reverse({closure_body})") + format!("{std_or_core}::cmp::Reverse({})", trigger.closure_body) } else { - closure_body.to_string() + trigger.closure_body }; + let closure_arg = snippet_with_applicability(cx, trigger.closure_arg, "_", &mut app); diag.span_suggestion( expr.span, "try", - format!("{recv}.{method}(|{}| {})", trigger.closure_arg, closure_body), + format!("{recv}.{method}(|{closure_arg}| {closure_body})"), app, ); }, diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 979170d72006..a0fa5e714289 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -438,6 +438,21 @@ pub fn peel_and_count_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize, Option, n: usize) -> (Ty<'_>, Option) { + let mut mutbl = None; + for _ in 0..n { + if let ty::Ref(_, dest_ty, m) = ty.kind() { + ty = *dest_ty; + mutbl.replace(mutbl.map_or(*m, |mutbl: Mutability| mutbl.min(*m))); + } else { + break; + } + } + (ty, mutbl) +} + /// Checks whether `a` and `b` are same types having same `Const` generic args, but ignores /// lifetimes. /// diff --git a/tests/ui/unnecessary_sort_by.fixed b/tests/ui/unnecessary_sort_by.fixed index 372cf683f223..6870470e74c5 100644 --- a/tests/ui/unnecessary_sort_by.fixed +++ b/tests/ui/unnecessary_sort_by.fixed @@ -141,3 +141,35 @@ fn issue16405() { items.sort_by_key(|item1| item1.value.clone()); //~^ unnecessary_sort_by } + +fn issue16348() { + let mut v: Vec<(i32, &str)> = vec![(1, "foo"), (2, "bar")]; + v.sort_by_key(|(_, s1)| *s1); + //~^ unnecessary_sort_by + + struct Foo { + bar: i32, + } + let mut v: Vec = vec![Foo { bar: 1 }, Foo { bar: 2 }]; + v.sort_by_key(|Foo { bar: b1 }| *b1); + //~^ unnecessary_sort_by + + struct Baz(i32); + let mut v: Vec = vec![Baz(1), Baz(2)]; + v.sort_by_key(|Baz(b1)| *b1); + //~^ unnecessary_sort_by + + v.sort_by_key(|&Baz(b1)| b1); + //~^ unnecessary_sort_by + + let mut v: Vec<&i32> = vec![&1, &2]; + v.sort_by_key(|&&b1| b1); + //~^ unnecessary_sort_by + + let mut v: Vec<[i32; 2]> = vec![[1, 2], [3, 4]]; + v.sort_by_key(|[a1, b1]| *a1); + //~^ unnecessary_sort_by + + v.sort_by_key(|[a1, b1]| a1 - b1); + //~^ unnecessary_sort_by +} diff --git a/tests/ui/unnecessary_sort_by.rs b/tests/ui/unnecessary_sort_by.rs index 7dd274623b35..d95306176817 100644 --- a/tests/ui/unnecessary_sort_by.rs +++ b/tests/ui/unnecessary_sort_by.rs @@ -141,3 +141,35 @@ fn issue16405() { items.sort_by(|item1, item2| item1.value.clone().cmp(&item2.value.clone())); //~^ unnecessary_sort_by } + +fn issue16348() { + let mut v: Vec<(i32, &str)> = vec![(1, "foo"), (2, "bar")]; + v.sort_by(|(_, s1), (_, s2)| s1.cmp(s2)); + //~^ unnecessary_sort_by + + struct Foo { + bar: i32, + } + let mut v: Vec = vec![Foo { bar: 1 }, Foo { bar: 2 }]; + v.sort_by(|Foo { bar: b1 }, Foo { bar: b2 }| b1.cmp(b2)); + //~^ unnecessary_sort_by + + struct Baz(i32); + let mut v: Vec = vec![Baz(1), Baz(2)]; + v.sort_by(|Baz(b1), Baz(b2)| b1.cmp(b2)); + //~^ unnecessary_sort_by + + v.sort_by(|&Baz(b1), &Baz(b2)| b1.cmp(&b2)); + //~^ unnecessary_sort_by + + let mut v: Vec<&i32> = vec![&1, &2]; + v.sort_by(|&&b1, &&b2| b1.cmp(&b2)); + //~^ unnecessary_sort_by + + let mut v: Vec<[i32; 2]> = vec![[1, 2], [3, 4]]; + v.sort_by(|[a1, b1], [a2, b2]| a1.cmp(a2)); + //~^ unnecessary_sort_by + + v.sort_by(|[a1, b1], [a2, b2]| (a1 - b1).cmp(&(a2 - b2))); + //~^ unnecessary_sort_by +} diff --git a/tests/ui/unnecessary_sort_by.stderr b/tests/ui/unnecessary_sort_by.stderr index b555245b0d01..b23d27c3729f 100644 --- a/tests/ui/unnecessary_sort_by.stderr +++ b/tests/ui/unnecessary_sort_by.stderr @@ -91,5 +91,47 @@ error: consider using `sort_by_key` LL | items.sort_by(|item1, item2| item1.value.clone().cmp(&item2.value.clone())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `items.sort_by_key(|item1| item1.value.clone())` -error: aborting due to 15 previous errors +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:147:5 + | +LL | v.sort_by(|(_, s1), (_, s2)| s1.cmp(s2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|(_, s1)| *s1)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:154:5 + | +LL | v.sort_by(|Foo { bar: b1 }, Foo { bar: b2 }| b1.cmp(b2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|Foo { bar: b1 }| *b1)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:159:5 + | +LL | v.sort_by(|Baz(b1), Baz(b2)| b1.cmp(b2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|Baz(b1)| *b1)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:162:5 + | +LL | v.sort_by(|&Baz(b1), &Baz(b2)| b1.cmp(&b2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|&Baz(b1)| b1)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:166:5 + | +LL | v.sort_by(|&&b1, &&b2| b1.cmp(&b2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|&&b1| b1)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:170:5 + | +LL | v.sort_by(|[a1, b1], [a2, b2]| a1.cmp(a2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|[a1, b1]| *a1)` + +error: consider using `sort_by_key` + --> tests/ui/unnecessary_sort_by.rs:173:5 + | +LL | v.sort_by(|[a1, b1], [a2, b2]| (a1 - b1).cmp(&(a2 - b2))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|[a1, b1]| a1 - b1)` + +error: aborting due to 22 previous errors From 5937b8ba10651eef9e328c6d6f054ba09726ead2 Mon Sep 17 00:00:00 2001 From: linshuy2 Date: Fri, 16 Jan 2026 20:33:32 +0000 Subject: [PATCH 094/273] Apply `unnecessary_sort_by` to Clippy itself --- clippy_utils/src/ty/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index a0fa5e714289..747dc1f13ab5 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -830,7 +830,7 @@ impl AdtVariantInfo { .enumerate() .map(|(i, f)| (i, approx_ty_size(cx, f.ty(cx.tcx, subst)))) .collect::>(); - fields_size.sort_by(|(_, a_size), (_, b_size)| a_size.cmp(b_size)); + fields_size.sort_by_key(|(_, a_size)| *a_size); Self { ind: i, From 196c098d24f54b661f76c2f9a3e1d74b8a5ce9b7 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 16 Jan 2026 22:42:53 +0100 Subject: [PATCH 095/273] `unnecessary_sort_by`: reduce suggestion diffs Now, only `call_span` is replaced, so the receiver is not a part of the diff. This also removes the need to create a snippet for the receiver. --- clippy_lints/src/methods/mod.rs | 4 +- .../src/methods/unnecessary_sort_by.rs | 18 +- tests/ui/unnecessary_sort_by.stderr | 175 +++++++++++++++--- tests/ui/unnecessary_sort_by_no_std.stderr | 15 +- 4 files changed, 178 insertions(+), 34 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 659a704a1ecb..376e93aa7e7d 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5532,10 +5532,10 @@ impl Methods { stable_sort_primitive::check(cx, expr, recv); }, (sym::sort_by, [arg]) => { - unnecessary_sort_by::check(cx, expr, recv, arg, false); + unnecessary_sort_by::check(cx, expr, call_span, arg, false); }, (sym::sort_unstable_by, [arg]) => { - unnecessary_sort_by::check(cx, expr, recv, arg, true); + unnecessary_sort_by::check(cx, expr, call_span, arg, true); }, (sym::split, [arg]) => { str_split::check(cx, expr, recv, arg); diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index ac13fc0b6850..3f81a6ecd2f8 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -343,7 +343,7 @@ fn expr_is_field_access(expr: &Expr<'_>) -> bool { pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, - recv: &'tcx Expr<'_>, + call_span: Span, arg: &'tcx Expr<'_>, is_unstable: bool, ) { @@ -367,17 +367,16 @@ pub(super) fn check<'tcx>( format!("consider using `{method}`"), |diag| { let mut app = trigger.applicability; - let recv = Sugg::hir_with_applicability(cx, recv, "(_)", &mut app); let closure_body = if trigger.reverse { format!("{std_or_core}::cmp::Reverse({})", trigger.closure_body) } else { trigger.closure_body }; let closure_arg = snippet_with_applicability(cx, trigger.closure_arg, "_", &mut app); - diag.span_suggestion( - expr.span, + diag.span_suggestion_verbose( + call_span, "try", - format!("{recv}.{method}(|{closure_arg}| {closure_body})"), + format!("{method}(|{closure_arg}| {closure_body})"), app, ); }, @@ -391,9 +390,12 @@ pub(super) fn check<'tcx>( expr.span, format!("consider using `{method}`"), |diag| { - let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_applicability(cx, recv, "(_)", &mut app); - diag.span_suggestion(expr.span, "try", format!("{recv}.{method}()"), app); + diag.span_suggestion_verbose( + call_span, + "try", + format!("{method}()"), + Applicability::MachineApplicable, + ); }, ); }, diff --git a/tests/ui/unnecessary_sort_by.stderr b/tests/ui/unnecessary_sort_by.stderr index b23d27c3729f..cc545d604ff3 100644 --- a/tests/ui/unnecessary_sort_by.stderr +++ b/tests/ui/unnecessary_sort_by.stderr @@ -2,136 +2,267 @@ error: consider using `sort` --> tests/ui/unnecessary_sort_by.rs:12:5 | LL | vec.sort_by(|a, b| a.cmp(b)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-sort-by` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_sort_by)]` +help: try + | +LL - vec.sort_by(|a, b| a.cmp(b)); +LL + vec.sort(); + | error: consider using `sort_unstable` --> tests/ui/unnecessary_sort_by.rs:14:5 | LL | vec.sort_unstable_by(|a, b| a.cmp(b)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_unstable_by(|a, b| a.cmp(b)); +LL + vec.sort_unstable(); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:16:5 | LL | vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (a + 5).abs())` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); +LL + vec.sort_by_key(|a| (a + 5).abs()); + | error: consider using `sort_unstable_by_key` --> tests/ui/unnecessary_sort_by.rs:18:5 | LL | vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| id(-a))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b))); +LL + vec.sort_unstable_by_key(|a| id(-a)); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:22:5 | LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| std::cmp::Reverse((b + 5).abs()))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); +LL + vec.sort_by_key(|b| std::cmp::Reverse((b + 5).abs())); + | error: consider using `sort_unstable_by_key` --> tests/ui/unnecessary_sort_by.rs:24:5 | LL | vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|b| std::cmp::Reverse(id(-b)))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a))); +LL + vec.sort_unstable_by_key(|b| std::cmp::Reverse(id(-b))); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:35:5 | LL | vec.sort_by(|a, b| (***a).abs().cmp(&(***b).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (***a).abs())` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_by(|a, b| (***a).abs().cmp(&(***b).abs())); +LL + vec.sort_by_key(|a| (***a).abs()); + | error: consider using `sort_unstable_by_key` --> tests/ui/unnecessary_sort_by.rs:37:5 | LL | vec.sort_unstable_by(|a, b| (***a).abs().cmp(&(***b).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| (***a).abs())` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_unstable_by(|a, b| (***a).abs().cmp(&(***b).abs())); +LL + vec.sort_unstable_by_key(|a| (***a).abs()); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:97:9 | LL | args.sort_by(|a, b| a.name().cmp(&b.name())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|a| a.name())` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - args.sort_by(|a, b| a.name().cmp(&b.name())); +LL + args.sort_by_key(|a| a.name()); + | error: consider using `sort_unstable_by_key` --> tests/ui/unnecessary_sort_by.rs:99:9 | LL | args.sort_unstable_by(|a, b| a.name().cmp(&b.name())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|a| a.name())` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - args.sort_unstable_by(|a, b| a.name().cmp(&b.name())); +LL + args.sort_unstable_by_key(|a| a.name()); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:102:9 | LL | args.sort_by(|a, b| b.name().cmp(&a.name())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|b| std::cmp::Reverse(b.name()))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - args.sort_by(|a, b| b.name().cmp(&a.name())); +LL + args.sort_by_key(|b| std::cmp::Reverse(b.name())); + | error: consider using `sort_unstable_by_key` --> tests/ui/unnecessary_sort_by.rs:104:9 | LL | args.sort_unstable_by(|a, b| b.name().cmp(&a.name())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name()))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - args.sort_unstable_by(|a, b| b.name().cmp(&a.name())); +LL + args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name())); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:118:5 | LL | v.sort_by(|a, b| a.0.cmp(&b.0)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|a| a.0)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|a, b| a.0.cmp(&b.0)); +LL + v.sort_by_key(|a| a.0); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:136:5 | LL | items.sort_by(|item1, item2| item1.key.cmp(&item2.key)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `items.sort_by_key(|item1| item1.key)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - items.sort_by(|item1, item2| item1.key.cmp(&item2.key)); +LL + items.sort_by_key(|item1| item1.key); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:141:5 | LL | items.sort_by(|item1, item2| item1.value.clone().cmp(&item2.value.clone())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `items.sort_by_key(|item1| item1.value.clone())` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - items.sort_by(|item1, item2| item1.value.clone().cmp(&item2.value.clone())); +LL + items.sort_by_key(|item1| item1.value.clone()); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:147:5 | LL | v.sort_by(|(_, s1), (_, s2)| s1.cmp(s2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|(_, s1)| *s1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|(_, s1), (_, s2)| s1.cmp(s2)); +LL + v.sort_by_key(|(_, s1)| *s1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:154:5 | LL | v.sort_by(|Foo { bar: b1 }, Foo { bar: b2 }| b1.cmp(b2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|Foo { bar: b1 }| *b1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|Foo { bar: b1 }, Foo { bar: b2 }| b1.cmp(b2)); +LL + v.sort_by_key(|Foo { bar: b1 }| *b1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:159:5 | LL | v.sort_by(|Baz(b1), Baz(b2)| b1.cmp(b2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|Baz(b1)| *b1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|Baz(b1), Baz(b2)| b1.cmp(b2)); +LL + v.sort_by_key(|Baz(b1)| *b1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:162:5 | LL | v.sort_by(|&Baz(b1), &Baz(b2)| b1.cmp(&b2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|&Baz(b1)| b1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|&Baz(b1), &Baz(b2)| b1.cmp(&b2)); +LL + v.sort_by_key(|&Baz(b1)| b1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:166:5 | LL | v.sort_by(|&&b1, &&b2| b1.cmp(&b2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|&&b1| b1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|&&b1, &&b2| b1.cmp(&b2)); +LL + v.sort_by_key(|&&b1| b1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:170:5 | LL | v.sort_by(|[a1, b1], [a2, b2]| a1.cmp(a2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|[a1, b1]| *a1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|[a1, b1], [a2, b2]| a1.cmp(a2)); +LL + v.sort_by_key(|[a1, b1]| *a1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by.rs:173:5 | LL | v.sort_by(|[a1, b1], [a2, b2]| (a1 - b1).cmp(&(a2 - b2))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `v.sort_by_key(|[a1, b1]| a1 - b1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - v.sort_by(|[a1, b1], [a2, b2]| (a1 - b1).cmp(&(a2 - b2))); +LL + v.sort_by_key(|[a1, b1]| a1 - b1); + | error: aborting due to 22 previous errors diff --git a/tests/ui/unnecessary_sort_by_no_std.stderr b/tests/ui/unnecessary_sort_by_no_std.stderr index de3ef4123514..b4dd6a6dbdc5 100644 --- a/tests/ui/unnecessary_sort_by_no_std.stderr +++ b/tests/ui/unnecessary_sort_by_no_std.stderr @@ -2,16 +2,27 @@ error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by_no_std.rs:10:5 | LL | vec.sort_by(|a, b| (a + 1).cmp(&(b + 1))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| a + 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-sort-by` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_sort_by)]` +help: try + | +LL - vec.sort_by(|a, b| (a + 1).cmp(&(b + 1))); +LL + vec.sort_by_key(|a| a + 1); + | error: consider using `sort_by_key` --> tests/ui/unnecessary_sort_by_no_std.rs:19:5 | LL | vec.sort_by(|a, b| (b + 1).cmp(&(a + 1))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| core::cmp::Reverse(b + 1))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - vec.sort_by(|a, b| (b + 1).cmp(&(a + 1))); +LL + vec.sort_by_key(|b| core::cmp::Reverse(b + 1)); + | error: aborting due to 2 previous errors From bcde8c10707c521480d1beb1bf4dd0fc72cc3902 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 17 Jan 2026 00:00:06 +0100 Subject: [PATCH 096/273] Do not consider binary operators as commutative by default Only `==` (and thus `!=`) are supposed to be commutative according to Rust's documentation. Do not make assumptions about other operators whose meaning may depend on the types on which they apply. However, special-case operators known to be commutative for primitive types such as addition or multiplication. --- clippy_utils/src/hir_utils.rs | 31 ++-- tests/ui/if_same_then_else.rs | 84 +++++++++-- tests/ui/if_same_then_else.stderr | 230 ++++++++++++++++++++++++++++-- 3 files changed, 309 insertions(+), 36 deletions(-) diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index bdc1550d69b7..79994d8a3c23 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -505,7 +505,7 @@ impl HirEqInterExpr<'_, '_, '_> { (ExprKind::Block(l, _), ExprKind::Block(r, _)) => self.eq_block(l, r), (ExprKind::Binary(l_op, ll, lr), ExprKind::Binary(r_op, rl, rr)) => { l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) - || swap_binop(l_op.node, ll, lr).is_some_and(|(l_op, ll, lr)| { + || swap_binop(self.inner.cx, l_op.node, ll, lr).is_some_and(|(l_op, ll, lr)| { l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) }) }, @@ -939,26 +939,35 @@ fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &' } fn swap_binop<'a>( + cx: &LateContext<'_>, binop: BinOpKind, lhs: &'a Expr<'a>, rhs: &'a Expr<'a>, ) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> { match binop { - BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => { - Some((binop, rhs, lhs)) - }, + // `==` and `!=`, are commutative + BinOpKind::Eq | BinOpKind::Ne => Some((binop, rhs, lhs)), + // Comparisons can be reversed BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)), BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)), BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)), BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)), - BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698 - | BinOpKind::Shl - | BinOpKind::Shr - | BinOpKind::Rem - | BinOpKind::Sub - | BinOpKind::Div + // Non-commutative operators + BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Rem | BinOpKind::Sub | BinOpKind::Div => None, + // We know that those operators are commutative for primitive types, + // and we don't assume anything for other types + BinOpKind::Mul + | BinOpKind::Add | BinOpKind::And - | BinOpKind::Or => None, + | BinOpKind::Or + | BinOpKind::BitAnd + | BinOpKind::BitXor + | BinOpKind::BitOr => cx + .typeck_results() + .expr_ty_adjusted(lhs) + .peel_refs() + .is_primitive() + .then_some((binop, rhs, lhs)), } } diff --git a/tests/ui/if_same_then_else.rs b/tests/ui/if_same_then_else.rs index 6d2e63e7299a..6e0e2c5ea720 100644 --- a/tests/ui/if_same_then_else.rs +++ b/tests/ui/if_same_then_else.rs @@ -11,6 +11,8 @@ unreachable_code )] +use std::ops::*; + struct Foo { bar: u8, } @@ -133,18 +135,6 @@ fn func() { fn f(val: &[u8]) {} -mod issue_5698 { - fn mul_not_always_commutative(x: i32, y: i32) -> i32 { - if x == 42 { - x * y - } else if x == 21 { - y * x - } else { - 0 - } - } -} - mod issue_8836 { fn do_not_lint() { if true { @@ -245,3 +235,73 @@ mod issue_11213 { } fn main() {} + +fn issue16416(x: bool, a: T, b: T) +where + T: Add + Sub + Mul + Div + Rem + BitAnd + BitOr + BitXor + PartialEq + Eq + PartialOrd + Ord + Shr + Shl + Copy, +{ + // Non-guaranteed-commutative operators + _ = if x { a * b } else { b * a }; + _ = if x { a + b } else { b + a }; + _ = if x { a - b } else { b - a }; + _ = if x { a / b } else { b / a }; + _ = if x { a % b } else { b % a }; + _ = if x { a << b } else { b << a }; + _ = if x { a >> b } else { b >> a }; + _ = if x { a & b } else { b & a }; + _ = if x { a ^ b } else { b ^ a }; + _ = if x { a | b } else { b | a }; + + // Guaranteed commutative operators + //~v if_same_then_else + _ = if x { a == b } else { b == a }; + //~v if_same_then_else + _ = if x { a != b } else { b != a }; + + // Symetric operators + //~v if_same_then_else + _ = if x { a < b } else { b > a }; + //~v if_same_then_else + _ = if x { a <= b } else { b >= a }; + //~v if_same_then_else + _ = if x { a > b } else { b < a }; + //~v if_same_then_else + _ = if x { a >= b } else { b <= a }; +} + +fn issue16416_prim(x: bool, a: u32, b: u32) { + // Non-commutative operators + _ = if x { a - b } else { b - a }; + _ = if x { a / b } else { b / a }; + _ = if x { a % b } else { b % a }; + _ = if x { a << b } else { b << a }; + _ = if x { a >> b } else { b >> a }; + + // Commutative operators on primitive types + //~v if_same_then_else + _ = if x { a * b } else { b * a }; + //~v if_same_then_else + _ = if x { a + b } else { b + a }; + //~v if_same_then_else + _ = if x { a & b } else { b & a }; + //~v if_same_then_else + _ = if x { a ^ b } else { b ^ a }; + //~v if_same_then_else + _ = if x { a | b } else { b | a }; + + // Always commutative operators + //~v if_same_then_else + _ = if x { a == b } else { b == a }; + //~v if_same_then_else + _ = if x { a != b } else { b != a }; + + // Symetric operators + //~v if_same_then_else + _ = if x { a < b } else { b > a }; + //~v if_same_then_else + _ = if x { a <= b } else { b >= a }; + //~v if_same_then_else + _ = if x { a > b } else { b < a }; + //~v if_same_then_else + _ = if x { a >= b } else { b <= a }; +} diff --git a/tests/ui/if_same_then_else.stderr b/tests/ui/if_same_then_else.stderr index b76da3fb1cb5..57396a566941 100644 --- a/tests/ui/if_same_then_else.stderr +++ b/tests/ui/if_same_then_else.stderr @@ -1,5 +1,5 @@ error: this `if` has identical blocks - --> tests/ui/if_same_then_else.rs:23:13 + --> tests/ui/if_same_then_else.rs:25:13 | LL | if true { | _____________^ @@ -12,7 +12,7 @@ LL | | } else { | |_____^ | note: same as this - --> tests/ui/if_same_then_else.rs:31:12 + --> tests/ui/if_same_then_else.rs:33:12 | LL | } else { | ____________^ @@ -27,43 +27,43 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::if_same_then_else)]` error: this `if` has identical blocks - --> tests/ui/if_same_then_else.rs:67:21 + --> tests/ui/if_same_then_else.rs:69:21 | LL | let _ = if true { 0.0 } else { 0.0 }; | ^^^^^^^ | note: same as this - --> tests/ui/if_same_then_else.rs:67:34 + --> tests/ui/if_same_then_else.rs:69:34 | LL | let _ = if true { 0.0 } else { 0.0 }; | ^^^^^^^ error: this `if` has identical blocks - --> tests/ui/if_same_then_else.rs:70:21 + --> tests/ui/if_same_then_else.rs:72:21 | LL | let _ = if true { -0.0 } else { -0.0 }; | ^^^^^^^^ | note: same as this - --> tests/ui/if_same_then_else.rs:70:35 + --> tests/ui/if_same_then_else.rs:72:35 | LL | let _ = if true { -0.0 } else { -0.0 }; | ^^^^^^^^ error: this `if` has identical blocks - --> tests/ui/if_same_then_else.rs:82:21 + --> tests/ui/if_same_then_else.rs:84:21 | LL | let _ = if true { 42 } else { 42 }; | ^^^^^^ | note: same as this - --> tests/ui/if_same_then_else.rs:82:33 + --> tests/ui/if_same_then_else.rs:84:33 | LL | let _ = if true { 42 } else { 42 }; | ^^^^^^ error: this `if` has identical blocks - --> tests/ui/if_same_then_else.rs:85:13 + --> tests/ui/if_same_then_else.rs:87:13 | LL | if true { | _____________^ @@ -76,7 +76,7 @@ LL | | } else { | |_____^ | note: same as this - --> tests/ui/if_same_then_else.rs:92:12 + --> tests/ui/if_same_then_else.rs:94:12 | LL | } else { | ____________^ @@ -89,7 +89,7 @@ LL | | } | |_____^ error: this `if` has identical blocks - --> tests/ui/if_same_then_else.rs:238:14 + --> tests/ui/if_same_then_else.rs:228:14 | LL | if x { | ______________^ @@ -98,7 +98,7 @@ LL | | } else { | |_________^ | note: same as this - --> tests/ui/if_same_then_else.rs:240:16 + --> tests/ui/if_same_then_else.rs:230:16 | LL | } else { | ________________^ @@ -106,5 +106,209 @@ LL | | 0_u8.is_power_of_two() LL | | } | |_________^ -error: aborting due to 6 previous errors +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:257:14 + | +LL | _ = if x { a == b } else { b == a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:257:30 + | +LL | _ = if x { a == b } else { b == a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:259:14 + | +LL | _ = if x { a != b } else { b != a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:259:30 + | +LL | _ = if x { a != b } else { b != a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:263:14 + | +LL | _ = if x { a < b } else { b > a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:263:29 + | +LL | _ = if x { a < b } else { b > a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:265:14 + | +LL | _ = if x { a <= b } else { b >= a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:265:30 + | +LL | _ = if x { a <= b } else { b >= a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:267:14 + | +LL | _ = if x { a > b } else { b < a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:267:29 + | +LL | _ = if x { a > b } else { b < a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:269:14 + | +LL | _ = if x { a >= b } else { b <= a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:269:30 + | +LL | _ = if x { a >= b } else { b <= a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:282:14 + | +LL | _ = if x { a * b } else { b * a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:282:29 + | +LL | _ = if x { a * b } else { b * a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:284:14 + | +LL | _ = if x { a + b } else { b + a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:284:29 + | +LL | _ = if x { a + b } else { b + a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:286:14 + | +LL | _ = if x { a & b } else { b & a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:286:29 + | +LL | _ = if x { a & b } else { b & a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:288:14 + | +LL | _ = if x { a ^ b } else { b ^ a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:288:29 + | +LL | _ = if x { a ^ b } else { b ^ a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:290:14 + | +LL | _ = if x { a | b } else { b | a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:290:29 + | +LL | _ = if x { a | b } else { b | a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:294:14 + | +LL | _ = if x { a == b } else { b == a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:294:30 + | +LL | _ = if x { a == b } else { b == a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:296:14 + | +LL | _ = if x { a != b } else { b != a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:296:30 + | +LL | _ = if x { a != b } else { b != a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:300:14 + | +LL | _ = if x { a < b } else { b > a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:300:29 + | +LL | _ = if x { a < b } else { b > a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:302:14 + | +LL | _ = if x { a <= b } else { b >= a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:302:30 + | +LL | _ = if x { a <= b } else { b >= a }; + | ^^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:304:14 + | +LL | _ = if x { a > b } else { b < a }; + | ^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:304:29 + | +LL | _ = if x { a > b } else { b < a }; + | ^^^^^^^^^ + +error: this `if` has identical blocks + --> tests/ui/if_same_then_else.rs:306:14 + | +LL | _ = if x { a >= b } else { b <= a }; + | ^^^^^^^^^^ + | +note: same as this + --> tests/ui/if_same_then_else.rs:306:30 + | +LL | _ = if x { a >= b } else { b <= a }; + | ^^^^^^^^^^ + +error: aborting due to 23 previous errors From dd61de95e47de79c4676c528f275cd07654fe377 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 17 Jan 2026 00:41:20 +0100 Subject: [PATCH 097/273] Remove empty stderr file from tests --- tests/ui/manual_take_nocore.stderr | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/ui/manual_take_nocore.stderr diff --git a/tests/ui/manual_take_nocore.stderr b/tests/ui/manual_take_nocore.stderr deleted file mode 100644 index e69de29bb2d1..000000000000 From 696f616b78940380e34f06f6d094c17ab7c647a4 Mon Sep 17 00:00:00 2001 From: xtqqczze <45661989+xtqqczze@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:59:16 +0000 Subject: [PATCH 098/273] Remove known problems from `comparison_chain` This reverts commit e42ba4829c02e8308ae142b5a2fd5efb6ccf0a7b, reversing changes made to d75bc868caee77f1b29763f8ba5a00494ed52f6b. --- clippy_lints/src/comparison_chain.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 238ebd4a444c..a2ddf3dad7a2 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -17,10 +17,6 @@ declare_clippy_lint! { /// `if` is not guaranteed to be exhaustive and conditionals can get /// repetitive /// - /// ### Known problems - /// The match statement may be slower due to the compiler - /// not inlining the call to cmp. See issue [#5354](https://github.com/rust-lang/rust-clippy/issues/5354) - /// /// ### Example /// ```rust,ignore /// # fn a() {} From a0f9a15b4a916c92d51131418e9fe080c83f9d3c Mon Sep 17 00:00:00 2001 From: Andreas Liljeqvist Date: Sat, 17 Jan 2026 11:36:25 +0100 Subject: [PATCH 099/273] Fix is_ascii performance regression on AVX-512 CPUs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `[u8]::is_ascii()` is compiled with `-C target-cpu=native` on AVX-512 CPUs, LLVM generates inefficient code. Because `is_ascii` is marked `#[inline]`, it gets inlined and recompiled with the user's target settings. The previous implementation used a counting loop that LLVM auto-vectorizes to `pmovmskb` on SSE2, but with AVX-512 enabled, LLVM uses k-registers and extracts bits individually with ~31 `kshiftrd` instructions. This fix replaces the counting loop with explicit SSE2 intrinsics (`_mm_loadu_si128`, `_mm_or_si128`, `_mm_movemask_epi8`) for x86_64. `_mm_movemask_epi8` compiles to `pmovmskb`, forcing efficient codegen regardless of CPU features. Benchmark results on AMD Ryzen 5 7500F (Zen 4 with AVX-512): - Default build: ~73 GB/s → ~74 GB/s (no regression) - With -C target-cpu=native: ~3 GB/s → ~67 GB/s (22x improvement) The loongarch64 implementation retains the original counting loop since it doesn't have this issue. Regression from: https://github.com/rust-lang/rust/pull/130733 --- library/core/src/slice/ascii.rs | 86 +++++++++++++++++--- tests/assembly-llvm/slice-is-ascii-avx512.rs | 18 ++++ tests/codegen-llvm/slice-is-ascii.rs | 9 +- 3 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 tests/assembly-llvm/slice-is-ascii-avx512.rs diff --git a/library/core/src/slice/ascii.rs b/library/core/src/slice/ascii.rs index 3e8c553f9f15..c9e168d6cbf8 100644 --- a/library/core/src/slice/ascii.rs +++ b/library/core/src/slice/ascii.rs @@ -3,10 +3,7 @@ use core::ascii::EscapeDefault; use crate::fmt::{self, Write}; -#[cfg(not(any( - all(target_arch = "x86_64", target_feature = "sse2"), - all(target_arch = "loongarch64", target_feature = "lsx") -)))] +#[cfg(not(all(target_arch = "loongarch64", target_feature = "lsx")))] use crate::intrinsics::const_eval_select; use crate::{ascii, iter, ops}; @@ -463,19 +460,84 @@ const fn is_ascii(s: &[u8]) -> bool { ) } -/// ASCII test optimized to use the `pmovmskb` instruction on `x86-64` and the -/// `vmskltz.b` instruction on `loongarch64`. +/// SSE2 implementation using `_mm_movemask_epi8` (compiles to `pmovmskb`) to +/// avoid LLVM's broken AVX-512 auto-vectorization of counting loops. +/// +/// # Safety +/// Requires SSE2 support (guaranteed on x86_64). +#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] +#[target_feature(enable = "sse2")] +unsafe fn is_ascii_sse2(bytes: &[u8]) -> bool { + use crate::arch::x86_64::{__m128i, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128}; + + const CHUNK_SIZE: usize = 32; + + let mut i = 0; + + while i + CHUNK_SIZE <= bytes.len() { + // SAFETY: We have verified that `i + CHUNK_SIZE <= bytes.len()`. + let ptr = unsafe { bytes.as_ptr().add(i) }; + + // Load two 16-byte chunks and combine them. + // SAFETY: We verified `i + 32 <= len`, so ptr is valid for 32 bytes. + // `_mm_loadu_si128` allows unaligned loads. + let chunk1 = unsafe { _mm_loadu_si128(ptr as *const __m128i) }; + // SAFETY: Same as above - ptr.add(16) is within the valid 32-byte range. + let chunk2 = unsafe { _mm_loadu_si128(ptr.add(16) as *const __m128i) }; + + // OR them together - if any byte has the high bit set, the result will too + let combined = _mm_or_si128(chunk1, chunk2); + + // Create a mask from the MSBs of each byte. + // If any byte is >= 128, its MSB is 1, so the mask will be non-zero. + let mask = _mm_movemask_epi8(combined); + + if mask != 0 { + return false; + } + + i += CHUNK_SIZE; + } + + // Handle remaining bytes with simple loop + while i < bytes.len() { + if !bytes[i].is_ascii() { + return false; + } + i += 1; + } + + true +} + +/// ASCII test optimized to use the `pmovmskb` instruction on `x86-64`. +/// +/// Uses explicit SSE2 intrinsics to prevent LLVM from auto-vectorizing with +/// broken AVX-512 code that extracts mask bits one-by-one. +#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] +#[inline] +#[rustc_allow_const_fn_unstable(const_eval_select)] +const fn is_ascii(bytes: &[u8]) -> bool { + const_eval_select!( + @capture { bytes: &[u8] } -> bool: + if const { + is_ascii_simple(bytes) + } else { + // SAFETY: SSE2 is guaranteed available on x86_64 + unsafe { is_ascii_sse2(bytes) } + } + ) +} + +/// ASCII test optimized to use the `vmskltz.b` instruction on `loongarch64`. /// /// Other platforms are not likely to benefit from this code structure, so they /// use SWAR techniques to test for ASCII in `usize`-sized chunks. -#[cfg(any( - all(target_arch = "x86_64", target_feature = "sse2"), - all(target_arch = "loongarch64", target_feature = "lsx") -))] +#[cfg(all(target_arch = "loongarch64", target_feature = "lsx"))] #[inline] const fn is_ascii(bytes: &[u8]) -> bool { // Process chunks of 32 bytes at a time in the fast path to enable - // auto-vectorization and use of `pmovmskb`. Two 128-bit vector registers + // auto-vectorization and use of `vmskltz.b`. Two 128-bit vector registers // can be OR'd together and then the resulting vector can be tested for // non-ASCII bytes. const CHUNK_SIZE: usize = 32; @@ -485,7 +547,7 @@ const fn is_ascii(bytes: &[u8]) -> bool { while i + CHUNK_SIZE <= bytes.len() { let chunk_end = i + CHUNK_SIZE; - // Get LLVM to produce a `pmovmskb` instruction on x86-64 which + // Get LLVM to produce a `vmskltz.b` instruction on loongarch64 which // creates a mask from the most significant bit of each byte. // ASCII bytes are less than 128 (0x80), so their most significant // bit is unset. diff --git a/tests/assembly-llvm/slice-is-ascii-avx512.rs b/tests/assembly-llvm/slice-is-ascii-avx512.rs new file mode 100644 index 000000000000..d3a441fec96c --- /dev/null +++ b/tests/assembly-llvm/slice-is-ascii-avx512.rs @@ -0,0 +1,18 @@ +//@ only-x86_64 +//@ compile-flags: -C opt-level=3 -C target-cpu=znver4 +//@ compile-flags: -C llvm-args=-x86-asm-syntax=intel +//@ assembly-output: emit-asm +#![crate_type = "lib"] + +// Verify is_ascii uses pmovmskb/vpmovmskb instead of kshiftrd with AVX-512. +// The fix uses explicit SSE2 intrinsics to avoid LLVM's broken auto-vectorization. +// +// See: https://github.com/rust-lang/rust/issues/129293 + +// CHECK-LABEL: test_is_ascii +#[no_mangle] +pub fn test_is_ascii(s: &[u8]) -> bool { + // CHECK-NOT: kshiftrd + // CHECK-NOT: kshiftrq + s.is_ascii() +} diff --git a/tests/codegen-llvm/slice-is-ascii.rs b/tests/codegen-llvm/slice-is-ascii.rs index 67537c871a0a..1f41b69e4396 100644 --- a/tests/codegen-llvm/slice-is-ascii.rs +++ b/tests/codegen-llvm/slice-is-ascii.rs @@ -1,10 +1,13 @@ -//@ only-x86_64 -//@ compile-flags: -C opt-level=3 -C target-cpu=x86-64 +//@ only-loongarch64 +//@ compile-flags: -C opt-level=3 #![crate_type = "lib"] -/// Check that the fast-path of `is_ascii` uses a `pmovmskb` instruction. +/// Check that the fast-path of `is_ascii` uses a `vmskltz.b` instruction. /// Platforms lacking an equivalent instruction use other techniques for /// optimizing `is_ascii`. +/// +/// Note: x86_64 uses explicit SSE2 intrinsics instead of relying on +/// auto-vectorization. See `slice-is-ascii-avx512.rs`. // CHECK-LABEL: @is_ascii_autovectorized #[no_mangle] pub fn is_ascii_autovectorized(s: &[u8]) -> bool { From c5785601773223450e8d1b8d553cac32de01840f Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 28 Nov 2025 14:07:18 +0800 Subject: [PATCH 100/273] Surpress suggestion from unstable items on stable channel --- compiler/rustc_resolve/src/diagnostics.rs | 1 + .../issue-149402-suggest-unresolve/foo.rs | 6 ++++ .../nightly.err | 18 ++++++++++++ .../output.diff | 16 ++++++++++ .../issue-149402-suggest-unresolve/rmake.rs | 29 +++++++++++++++++++ .../issue-149402-suggest-unresolve/stable.err | 9 ++++++ 6 files changed, 79 insertions(+) create mode 100644 tests/run-make/issue-149402-suggest-unresolve/foo.rs create mode 100644 tests/run-make/issue-149402-suggest-unresolve/nightly.err create mode 100644 tests/run-make/issue-149402-suggest-unresolve/output.diff create mode 100644 tests/run-make/issue-149402-suggest-unresolve/rmake.rs create mode 100644 tests/run-make/issue-149402-suggest-unresolve/stable.err diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index d704280f3fa2..9fc32b31b32a 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1610,6 +1610,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } + suggestions.retain(|suggestion| suggestion.is_stable || self.tcx.sess.is_nightly_build()); suggestions } diff --git a/tests/run-make/issue-149402-suggest-unresolve/foo.rs b/tests/run-make/issue-149402-suggest-unresolve/foo.rs new file mode 100644 index 000000000000..8456990829b4 --- /dev/null +++ b/tests/run-make/issue-149402-suggest-unresolve/foo.rs @@ -0,0 +1,6 @@ +fn foo() { + let x = Vec::new(); + x.push(Complete::Item { name: "hello" }); +} + +fn main() {} diff --git a/tests/run-make/issue-149402-suggest-unresolve/nightly.err b/tests/run-make/issue-149402-suggest-unresolve/nightly.err new file mode 100644 index 000000000000..8659f0170df3 --- /dev/null +++ b/tests/run-make/issue-149402-suggest-unresolve/nightly.err @@ -0,0 +1,18 @@ +error[E0433]: failed to resolve: use of undeclared type `Complete` + --> foo.rs:3:12 + | +3 | x.push(Complete::Item { name: "hello" }); + | ^^^^^^^^ use of undeclared type `Complete` + | +help: there is an enum variant `core::ops::CoroutineState::Complete` and 1 other; try using the variant's enum + | +3 - x.push(Complete::Item { name: "hello" }); +3 + x.push(core::ops::CoroutineState::Item { name: "hello" }); + | +3 - x.push(Complete::Item { name: "hello" }); +3 + x.push(std::ops::CoroutineState::Item { name: "hello" }); + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0433`. diff --git a/tests/run-make/issue-149402-suggest-unresolve/output.diff b/tests/run-make/issue-149402-suggest-unresolve/output.diff new file mode 100644 index 000000000000..196c0326a714 --- /dev/null +++ b/tests/run-make/issue-149402-suggest-unresolve/output.diff @@ -0,0 +1,16 @@ +@@ -3,6 +3,15 @@ + | + 3 | x.push(Complete::Item { name: "hello" }); + | ^^^^^^^^ use of undeclared type `Complete` ++ | ++help: there is an enum variant `core::ops::CoroutineState::Complete` and 1 other; try using the variant's enum ++ | ++3 - x.push(Complete::Item { name: "hello" }); ++3 + x.push(core::ops::CoroutineState::Item { name: "hello" }); ++ | ++3 - x.push(Complete::Item { name: "hello" }); ++3 + x.push(std::ops::CoroutineState::Item { name: "hello" }); ++ | + + error: aborting due to 1 previous error + diff --git a/tests/run-make/issue-149402-suggest-unresolve/rmake.rs b/tests/run-make/issue-149402-suggest-unresolve/rmake.rs new file mode 100644 index 000000000000..5bca0c0206cb --- /dev/null +++ b/tests/run-make/issue-149402-suggest-unresolve/rmake.rs @@ -0,0 +1,29 @@ +//! Check that unstable name-resolution suggestions are omitted on stable. +//! +//! Regression test for . +//! +//@ only-nightly +//@ needs-target-std + +use run_make_support::{diff, rustc, similar}; + +fn main() { + let stable_like = rustc() + .env("RUSTC_BOOTSTRAP", "-1") + .edition("2024") + .input("foo.rs") + .run_fail() + .stderr_utf8(); + + assert!(!stable_like.contains("CoroutineState::Complete")); + diff().expected_file("stable.err").actual_text("stable_like", &stable_like).run(); + + let nightly = rustc().edition("2024").input("foo.rs").run_fail().stderr_utf8(); + + assert!(nightly.contains("CoroutineState::Complete")); + diff().expected_file("nightly.err").actual_text("nightly", &nightly).run(); + + let stderr_diff = + similar::TextDiff::from_lines(&stable_like, &nightly).unified_diff().to_string(); + diff().expected_file("output.diff").actual_text("diff", stderr_diff).run(); +} diff --git a/tests/run-make/issue-149402-suggest-unresolve/stable.err b/tests/run-make/issue-149402-suggest-unresolve/stable.err new file mode 100644 index 000000000000..6e82fe1a67ea --- /dev/null +++ b/tests/run-make/issue-149402-suggest-unresolve/stable.err @@ -0,0 +1,9 @@ +error[E0433]: failed to resolve: use of undeclared type `Complete` + --> foo.rs:3:12 + | +3 | x.push(Complete::Item { name: "hello" }); + | ^^^^^^^^ use of undeclared type `Complete` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0433`. From 85200a4ef0e889c35473dad54a3b04ab8540fce6 Mon Sep 17 00:00:00 2001 From: Clara Engler Date: Fri, 16 Jan 2026 18:10:08 +0100 Subject: [PATCH 101/273] option: Use Option::map in Option::cloned This commit removes a repetitive match statement in favor of Option::map for Option::cloned. --- library/core/src/option.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/library/core/src/option.rs b/library/core/src/option.rs index ed31d4efaa75..eb4f978b7c19 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -2103,10 +2103,7 @@ impl Option<&T> { where T: Clone, { - match self { - Some(t) => Some(t.clone()), - None => None, - } + self.map(T::clone) } } @@ -2154,10 +2151,7 @@ impl Option<&mut T> { where T: Clone, { - match self { - Some(t) => Some(t.clone()), - None => None, - } + self.as_deref().map(T::clone) } } From 2b8f4a562f24afcb45f4f12b109c13beb5b45f75 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sun, 29 Sep 2024 00:27:50 +0200 Subject: [PATCH 102/273] avoid phi node for pointers flowing into Vec appends --- library/alloc/src/slice.rs | 11 ++++--- library/alloc/src/vec/mod.rs | 6 +++- .../lib-optimizations/append-elements.rs | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 tests/codegen-llvm/lib-optimizations/append-elements.rs diff --git a/library/alloc/src/slice.rs b/library/alloc/src/slice.rs index e7d0fc3454ee..634747ca3f84 100644 --- a/library/alloc/src/slice.rs +++ b/library/alloc/src/slice.rs @@ -444,13 +444,16 @@ impl [T] { impl ConvertVec for T { #[inline] fn to_vec(s: &[Self], alloc: A) -> Vec { - let mut v = Vec::with_capacity_in(s.len(), alloc); + let len = s.len(); + let mut v = Vec::with_capacity_in(len, alloc); // SAFETY: // allocated above with the capacity of `s`, and initialize to `s.len()` in // ptr::copy_to_non_overlapping below. - unsafe { - s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), s.len()); - v.set_len(s.len()); + if len > 0 { + unsafe { + s.as_ptr().copy_to_nonoverlapping(v.as_mut_ptr(), len); + v.set_len(len); + } } v } diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 379e964f0a0c..ac86399df7ab 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2818,7 +2818,11 @@ impl Vec { let count = other.len(); self.reserve(count); let len = self.len(); - unsafe { ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) }; + if count > 0 { + unsafe { + ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) + }; + } self.len += count; } diff --git a/tests/codegen-llvm/lib-optimizations/append-elements.rs b/tests/codegen-llvm/lib-optimizations/append-elements.rs new file mode 100644 index 000000000000..b8657104d665 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/append-elements.rs @@ -0,0 +1,33 @@ +//@ compile-flags: -O -Zmerge-functions=disabled +//@ needs-deterministic-layouts +//@ min-llvm-version: 21 +#![crate_type = "lib"] + +//! Check that a temporary intermediate allocations can eliminated and replaced +//! with memcpy forwarding. +//! This requires Vec code to be structured in a way that avoids phi nodes from the +//! zero-capacity length flowing into the memcpy arguments. + +// CHECK-LABEL: @vec_append_with_temp_alloc +// CHECK-SAME: ptr{{.*}}[[DST:%[a-z]+]]{{.*}}ptr{{.*}}[[SRC:%[a-z]+]] +#[no_mangle] +pub fn vec_append_with_temp_alloc(dst: &mut Vec, src: &[u8]) { + // CHECK-NOT: call void @llvm.memcpy + // CHECK: call void @llvm.memcpy.{{.*}}[[DST]].i{{.*}}[[SRC]] + // CHECK-NOT: call void @llvm.memcpy + let temp = src.to_vec(); + dst.extend(&temp); + // CHECK: ret +} + +// CHECK-LABEL: @string_append_with_temp_alloc +// CHECK-SAME: ptr{{.*}}[[DST:%[a-z]+]]{{.*}}ptr{{.*}}[[SRC:%[a-z]+]] +#[no_mangle] +pub fn string_append_with_temp_alloc(dst: &mut String, src: &str) { + // CHECK-NOT: call void @llvm.memcpy + // CHECK: call void @llvm.memcpy.{{.*}}[[DST]].i{{.*}}[[SRC]] + // CHECK-NOT: call void @llvm.memcpy + let temp = src.to_string(); + dst.push_str(&temp); + // CHECK: ret +} From 08432c892758a06a6bab9fa0584effb7e7881303 Mon Sep 17 00:00:00 2001 From: Andreas Liljeqvist Date: Sun, 18 Jan 2026 22:49:37 +0100 Subject: [PATCH 103/273] Optimize small input path for is_ascii on x86_64 For inputs smaller than 32 bytes, use usize-at-a-time processing instead of calling the SSE2 function. This avoids function call overhead from #[target_feature(enable = "sse2")] which prevents inlining. Also moves CHUNK_SIZE to module level so it can be shared between is_ascii and is_ascii_sse2. --- library/core/src/slice/ascii.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/library/core/src/slice/ascii.rs b/library/core/src/slice/ascii.rs index c9e168d6cbf8..25b8a10af355 100644 --- a/library/core/src/slice/ascii.rs +++ b/library/core/src/slice/ascii.rs @@ -460,6 +460,10 @@ const fn is_ascii(s: &[u8]) -> bool { ) } +/// Chunk size for vectorized ASCII checking (two 16-byte SSE registers). +#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] +const CHUNK_SIZE: usize = 32; + /// SSE2 implementation using `_mm_movemask_epi8` (compiles to `pmovmskb`) to /// avoid LLVM's broken AVX-512 auto-vectorization of counting loops. /// @@ -470,8 +474,6 @@ const fn is_ascii(s: &[u8]) -> bool { unsafe fn is_ascii_sse2(bytes: &[u8]) -> bool { use crate::arch::x86_64::{__m128i, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128}; - const CHUNK_SIZE: usize = 32; - let mut i = 0; while i + CHUNK_SIZE <= bytes.len() { @@ -518,11 +520,27 @@ unsafe fn is_ascii_sse2(bytes: &[u8]) -> bool { #[inline] #[rustc_allow_const_fn_unstable(const_eval_select)] const fn is_ascii(bytes: &[u8]) -> bool { + const USIZE_SIZE: usize = size_of::(); + const NONASCII_MASK: usize = usize::MAX / 255 * 0x80; + const_eval_select!( @capture { bytes: &[u8] } -> bool: if const { is_ascii_simple(bytes) } else { + // For small inputs, use usize-at-a-time processing to avoid SSE2 call overhead. + if bytes.len() < CHUNK_SIZE { + let chunks = bytes.chunks_exact(USIZE_SIZE); + let remainder = chunks.remainder(); + for chunk in chunks { + let word = usize::from_ne_bytes(chunk.try_into().unwrap()); + if (word & NONASCII_MASK) != 0 { + return false; + } + } + return remainder.iter().all(|b| b.is_ascii()); + } + // SAFETY: SSE2 is guaranteed available on x86_64 unsafe { is_ascii_sse2(bytes) } } From e519c686cfe9220609929e014642bf99eebb48c4 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 19 Jan 2026 11:15:20 +0800 Subject: [PATCH 104/273] Deduplicate diagnostics for const trait supertraits --- .../traits/fulfillment_errors.rs | 31 +++++++++++++++++++ tests/ui/consts/issue-94675.rs | 1 - tests/ui/consts/issue-94675.stderr | 13 +------- tests/ui/traits/const-traits/call.rs | 1 - tests/ui/traits/const-traits/call.stderr | 11 +------ ...closure-const_trait_impl-ice-113381.stderr | 11 +------ ...-const-op-const-closure-non-const-outer.rs | 3 +- ...st-op-const-closure-non-const-outer.stderr | 15 ++------- 8 files changed, 39 insertions(+), 47 deletions(-) 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 00d06779e652..6872d038fb7f 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 @@ -1442,6 +1442,31 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { self.can_eq(param_env, goal.trait_ref, trait_assumption.trait_ref) } + fn can_match_host_effect( + &self, + param_env: ty::ParamEnv<'tcx>, + goal: ty::HostEffectPredicate<'tcx>, + assumption: ty::Binder<'tcx, ty::HostEffectPredicate<'tcx>>, + ) -> bool { + let assumption = self.instantiate_binder_with_fresh_vars( + DUMMY_SP, + infer::BoundRegionConversionTime::HigherRankedType, + assumption, + ); + + assumption.constness.satisfies(goal.constness) + && self.can_eq(param_env, goal.trait_ref, assumption.trait_ref) + } + + fn as_host_effect_clause( + predicate: ty::Predicate<'tcx>, + ) -> Option>> { + predicate.as_clause().and_then(|clause| match clause.kind().skip_binder() { + ty::ClauseKind::HostEffect(pred) => Some(clause.kind().rebind(pred)), + _ => None, + }) + } + fn can_match_projection( &self, param_env: ty::ParamEnv<'tcx>, @@ -1484,6 +1509,12 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .filter_map(|implied| implied.as_trait_clause()) .any(|implied| self.can_match_trait(param_env, error, implied)) }) + } else if let Some(error) = Self::as_host_effect_clause(error.predicate) { + self.enter_forall(error, |error| { + elaborate(self.tcx, std::iter::once(cond.predicate)) + .filter_map(Self::as_host_effect_clause) + .any(|implied| self.can_match_host_effect(param_env, error, implied)) + }) } else if let Some(error) = error.predicate.as_projection_clause() { self.enter_forall(error, |error| { elaborate(self.tcx, std::iter::once(cond.predicate)) diff --git a/tests/ui/consts/issue-94675.rs b/tests/ui/consts/issue-94675.rs index f2ddc928d122..0553b676bc3e 100644 --- a/tests/ui/consts/issue-94675.rs +++ b/tests/ui/consts/issue-94675.rs @@ -10,7 +10,6 @@ impl<'a> Foo<'a> { const fn spam(&mut self, baz: &mut Vec) { self.bar[0] = baz.len(); //~^ ERROR: `Vec: [const] Index<_>` is not satisfied - //~| ERROR: `Vec: [const] Index` is not satisfied //~| ERROR: `Vec: [const] IndexMut` is not satisfied } } diff --git a/tests/ui/consts/issue-94675.stderr b/tests/ui/consts/issue-94675.stderr index 771f2a14c305..ab7a76a90e02 100644 --- a/tests/ui/consts/issue-94675.stderr +++ b/tests/ui/consts/issue-94675.stderr @@ -16,17 +16,6 @@ LL | self.bar[0] = baz.len(); note: trait `IndexMut` is implemented but not `const` --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL -error[E0277]: the trait bound `Vec: [const] Index` is not satisfied - --> $DIR/issue-94675.rs:11:9 - | -LL | self.bar[0] = baz.len(); - | ^^^^^^^^^^^ - | -note: trait `Index` is implemented but not `const` - --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL -note: required by a bound in `std::ops::IndexMut::index_mut` - --> $SRC_DIR/core/src/ops/index.rs:LL:COL - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/const-traits/call.rs b/tests/ui/traits/const-traits/call.rs index c36adc4248f0..360c08e1b7fe 100644 --- a/tests/ui/traits/const-traits/call.rs +++ b/tests/ui/traits/const-traits/call.rs @@ -6,7 +6,6 @@ const _: () = { assert!((const || true)()); //~^ ERROR }: [const] Fn()` is not satisfied - //~| ERROR }: [const] FnMut()` is not satisfied }; fn main() {} diff --git a/tests/ui/traits/const-traits/call.stderr b/tests/ui/traits/const-traits/call.stderr index b688746e2506..8e32cab6dfcf 100644 --- a/tests/ui/traits/const-traits/call.stderr +++ b/tests/ui/traits/const-traits/call.stderr @@ -4,15 +4,6 @@ error[E0277]: the trait bound `{closure@$DIR/call.rs:7:14: 7:22}: [const] Fn()` LL | assert!((const || true)()); | ^^^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `{closure@$DIR/call.rs:7:14: 7:22}: [const] FnMut()` is not satisfied - --> $DIR/call.rs:7:13 - | -LL | assert!((const || true)()); - | ^^^^^^^^^^^^^^^^^ - | -note: required by a bound in `call` - --> $SRC_DIR/core/src/ops/function.rs:LL:COL - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr b/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr index 90e87c724f54..dab3f14161fa 100644 --- a/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr +++ b/tests/ui/traits/const-traits/const_closure-const_trait_impl-ice-113381.stderr @@ -4,15 +4,6 @@ error[E0277]: the trait bound `{closure@$DIR/const_closure-const_trait_impl-ice- LL | (const || (()).foo())(); | ^^^^^^^^^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `{closure@$DIR/const_closure-const_trait_impl-ice-113381.rs:15:6: 15:14}: [const] FnMut()` is not satisfied - --> $DIR/const_closure-const_trait_impl-ice-113381.rs:15:5 - | -LL | (const || (()).foo())(); - | ^^^^^^^^^^^^^^^^^^^^^^^ - | -note: required by a bound in `call` - --> $SRC_DIR/core/src/ops/function.rs:LL:COL - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs index ee4ff02f4c7c..de5bedf0ace7 100644 --- a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs +++ b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.rs @@ -10,7 +10,8 @@ impl Foo for () { } fn main() { + // #150052 deduplicate diagnostics for const trait supertraits + // so we only get one error here (const || { (()).foo() })(); //~^ ERROR: }: [const] Fn()` is not satisfied - //~| ERROR: }: [const] FnMut()` is not satisfied } diff --git a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr index 69d289537da1..efbedca1c7e7 100644 --- a/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr +++ b/tests/ui/traits/const-traits/non-const-op-const-closure-non-const-outer.stderr @@ -1,18 +1,9 @@ -error[E0277]: the trait bound `{closure@$DIR/non-const-op-const-closure-non-const-outer.rs:13:6: 13:14}: [const] Fn()` is not satisfied - --> $DIR/non-const-op-const-closure-non-const-outer.rs:13:5 +error[E0277]: the trait bound `{closure@$DIR/non-const-op-const-closure-non-const-outer.rs:15:6: 15:14}: [const] Fn()` is not satisfied + --> $DIR/non-const-op-const-closure-non-const-outer.rs:15:5 | LL | (const || { (()).foo() })(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `{closure@$DIR/non-const-op-const-closure-non-const-outer.rs:13:6: 13:14}: [const] FnMut()` is not satisfied - --> $DIR/non-const-op-const-closure-non-const-outer.rs:13:5 - | -LL | (const || { (()).foo() })(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | -note: required by a bound in `call` - --> $SRC_DIR/core/src/ops/function.rs:LL:COL - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0277`. From 80c0b99de0e4fce49a186e507778fb179a87dff0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 19 Jan 2026 00:42:38 +0100 Subject: [PATCH 105/273] add `simd_splat` intrinsic --- .../src/intrinsics/simd.rs | 25 +++++++++ .../rustc_codegen_gcc/src/intrinsic/simd.rs | 29 +++++++++++ compiler/rustc_codegen_llvm/src/intrinsic.rs | 25 +++++++++ .../src/interpret/intrinsics/simd.rs | 9 ++++ .../rustc_hir_analysis/src/check/intrinsic.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics/simd.rs | 7 +++ tests/codegen-llvm/simd/splat.rs | 32 ++++++++++++ tests/ui/simd/intrinsic/splat.rs | 52 +++++++++++++++++++ 9 files changed, 181 insertions(+) create mode 100644 tests/codegen-llvm/simd/splat.rs create mode 100644 tests/ui/simd/intrinsic/splat.rs diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs index bef9c6747457..200cedf0f6ae 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs @@ -348,6 +348,31 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( ret.write_cvalue(fx, ret_lane); } + sym::simd_splat => { + intrinsic_args!(fx, args => (value); intrinsic); + + if !ret.layout().ty.is_simd() { + report_simd_type_validation_error(fx, intrinsic, span, ret.layout().ty); + return; + } + let (lane_count, lane_ty) = ret.layout().ty.simd_size_and_type(fx.tcx); + + if value.layout().ty != lane_ty { + fx.tcx.dcx().span_fatal( + span, + format!( + "[simd_splat] expected element type {lane_ty:?}, got {got:?}", + got = value.layout().ty + ), + ); + } + + for i in 0..lane_count { + let ret_lane = ret.place_lane(fx, i.into()); + ret_lane.write_cvalue(fx, value); + } + } + sym::simd_neg | sym::simd_bswap | sym::simd_bitreverse diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 39b4bb3ebefa..0606639d1731 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -121,6 +121,35 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(bx.vector_select(vector_mask, arg1, args[2].immediate())); } + if name == sym::simd_splat { + require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); + let (out_len, out_ty) = ret_ty.simd_size_and_type(bx.tcx()); + + require!( + args[0].layout.ty == out_ty, + InvalidMonomorphization::ExpectedVectorElementType { + span, + name, + expected_element: out_ty, + vector_type: ret_ty, + } + ); + + let vec_ty = llret_ty.unqualified().dyncast_vector().expect("vector return type"); + let elem_ty = vec_ty.get_element_type(); + + // Cast pointer type to usize (GCC does not support pointer SIMD vectors). + let scalar = args[0].immediate(); + let scalar = if scalar.get_type().unqualified() != elem_ty.unqualified() { + bx.ptrtoint(scalar, elem_ty) + } else { + scalar + }; + + let elements = vec![scalar; out_len as usize]; + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &elements)); + } + // every intrinsic below takes a SIMD vector as its first argument require_simd!( args[0].layout.ty, diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index c6aae89f1e51..a2e2acf58953 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1588,6 +1588,31 @@ fn generic_simd_intrinsic<'ll, 'tcx>( return Ok(bx.select(m_i1s, args[1].immediate(), args[2].immediate())); } + if name == sym::simd_splat { + let (_out_len, out_ty) = require_simd!(ret_ty, SimdReturn); + + require!( + args[0].layout.ty == out_ty, + InvalidMonomorphization::ExpectedVectorElementType { + span, + name, + expected_element: out_ty, + vector_type: ret_ty, + } + ); + + // `insertelement poison, elem %x, i32 0` + let poison_vec = bx.const_poison(llret_ty); + let idx0 = bx.const_i32(0); + let v0 = bx.insert_element(poison_vec, args[0].immediate(), idx0); + + // `shufflevector v0, poison, zeroinitializer` + // The masks is all zeros, so this splats lane 0 (which has our element in it). + let splat = bx.shuffle_vector(v0, poison_vec, bx.const_null(llret_ty)); + + return Ok(splat); + } + // every intrinsic below takes a SIMD vector as its first argument let (in_len, in_elem) = require_simd!(args[0].layout.ty, SimdInput); let in_ty = args[0].layout.ty; diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs index 33a115384a88..f9ba47daac5d 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs @@ -60,6 +60,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } self.copy_op(&self.project_index(&input, index)?, &dest)?; } + sym::simd_splat => { + let elem = &args[0]; + let (dest, dest_len) = self.project_to_simd(&dest)?; + + for i in 0..dest_len { + let place = self.project_index(&dest, i)?; + self.copy_op(elem, &place)?; + } + } sym::simd_neg | sym::simd_fabs | sym::simd_ceil diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index d3d167f6e254..f4deb1fc9029 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -746,6 +746,7 @@ pub(crate) fn check_intrinsic_type( sym::simd_extract | sym::simd_extract_dyn => { (2, 0, vec![param(0), tcx.types.u32], param(1)) } + sym::simd_splat => (2, 0, vec![param(1)], param(0)), sym::simd_cast | sym::simd_as | sym::simd_cast_ptr diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d21580c16db2..edf716cc0a86 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2140,6 +2140,7 @@ symbols! { simd_shr, simd_shuffle, simd_shuffle_const_generic, + simd_splat, simd_sub, simd_trunc, simd_with_exposed_provenance, diff --git a/library/core/src/intrinsics/simd.rs b/library/core/src/intrinsics/simd.rs index 722a765cd01e..9c24a2294797 100644 --- a/library/core/src/intrinsics/simd.rs +++ b/library/core/src/intrinsics/simd.rs @@ -59,6 +59,13 @@ pub unsafe fn simd_extract_dyn(x: T, idx: u32) -> U { unsafe { (&raw const x).cast::().add(idx as usize).read() } } +/// Creates a vector where every lane has the provided value. +/// +/// `T` must be a vector with element type `U`. +#[rustc_nounwind] +#[rustc_intrinsic] +pub const unsafe fn simd_splat(value: U) -> T; + /// Adds two simd vectors elementwise. /// /// `T` must be a vector of integers or floats. diff --git a/tests/codegen-llvm/simd/splat.rs b/tests/codegen-llvm/simd/splat.rs new file mode 100644 index 000000000000..f3e1866fd64a --- /dev/null +++ b/tests/codegen-llvm/simd/splat.rs @@ -0,0 +1,32 @@ +#![crate_type = "lib"] +#![no_std] +#![feature(repr_simd, core_intrinsics)] +use core::intrinsics::simd::simd_splat; + +#[path = "../../auxiliary/minisimd.rs"] +mod minisimd; +use minisimd::*; + +// Test that `simd_splat` produces the canonical LLVM splat sequence. + +#[no_mangle] +unsafe fn int(x: u16) -> u16x2 { + // CHECK-LABEL: int + // CHECK: start: + // CHECK-NEXT: %0 = insertelement <2 x i16> poison, i16 %x, i64 0 + // CHECK-NEXT: %1 = shufflevector <2 x i16> %0, <2 x i16> poison, <2 x i32> zeroinitializer + // CHECK-NEXT: store + // CHECK-NEXT: ret + simd_splat(x) +} + +#[no_mangle] +unsafe fn float(x: f32) -> f32x4 { + // CHECK-LABEL: float + // CHECK: start: + // CHECK-NEXT: %0 = insertelement <4 x float> poison, float %x, i64 0 + // CHECK-NEXT: %1 = shufflevector <4 x float> %0, <4 x float> poison, <4 x i32> zeroinitializer + // CHECK-NEXT: store + // CHECK-NEXT: ret + simd_splat(x) +} diff --git a/tests/ui/simd/intrinsic/splat.rs b/tests/ui/simd/intrinsic/splat.rs new file mode 100644 index 000000000000..af3d2270061d --- /dev/null +++ b/tests/ui/simd/intrinsic/splat.rs @@ -0,0 +1,52 @@ +//@ run-pass +#![feature(repr_simd, core_intrinsics)] + +#[path = "../../../auxiliary/minisimd.rs"] +mod minisimd; +use minisimd::*; + +use std::intrinsics::simd::simd_splat; + +fn main() { + unsafe { + let x: Simd = simd_splat(123u32); + let y: Simd = const { simd_splat(123u32) }; + assert_eq!(x.into_array(), [123; 1]); + assert_eq!(x.into_array(), y.into_array()); + + let x: u16x2 = simd_splat(42u16); + let y: u16x2 = const { simd_splat(42u16) }; + assert_eq!(x.into_array(), [42; 2]); + assert_eq!(x.into_array(), y.into_array()); + + let x: u128x4 = simd_splat(42u128); + let y: u128x4 = const { simd_splat(42u128) }; + assert_eq!(x.into_array(), [42; 4]); + assert_eq!(x.into_array(), y.into_array()); + + let x: i32x4 = simd_splat(-7i32); + let y: i32x4 = const { simd_splat(-7i32) }; + assert_eq!(x.into_array(), [-7; 4]); + assert_eq!(x.into_array(), y.into_array()); + + let x: f32x4 = simd_splat(42.0f32); + let y: f32x4 = const { simd_splat(42.0f32) }; + assert_eq!(x.into_array(), [42.0; 4]); + assert_eq!(x.into_array(), y.into_array()); + + let x: f64x2 = simd_splat(42.0f64); + let y: f64x2 = const { simd_splat(42.0f64) }; + assert_eq!(x.into_array(), [42.0; 2]); + assert_eq!(x.into_array(), y.into_array()); + + static ZERO: u8 = 0u8; + let x: Simd<*const u8, 2> = simd_splat(&raw const ZERO); + assert_eq!(x.into_array(), [&raw const ZERO; 2]); + + // FIXME: this hits "could not evaluate shuffle_indices at compile time", + // emitted in `immediate_const_vector`. const-eval should be able to handle + // this though I think? `const { [&raw const ZERO; 2] }` appears to work. + // let y: Simd<*const u8, 2> = const { simd_splat(&raw const ZERO) }; + // assert_eq!(x.into_array(), y.into_array()); + } +} From 120388c063b809d7d4f82b4bbf0077e6138d3e67 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 19 Jan 2026 15:44:11 +0100 Subject: [PATCH 106/273] `simd_splat`: custom error in gcc backend for invalid element type --- .../rustc_codegen_gcc/src/intrinsic/simd.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 0606639d1731..eab067a02b7b 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -121,9 +121,9 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(bx.vector_select(vector_mask, arg1, args[2].immediate())); } + #[cfg(feature = "master")] if name == sym::simd_splat { - require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); - let (out_len, out_ty) = ret_ty.simd_size_and_type(bx.tcx()); + let (out_len, out_ty) = require_simd2!(ret_ty, SimdReturn); require!( args[0].layout.ty == out_ty, @@ -139,11 +139,18 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let elem_ty = vec_ty.get_element_type(); // Cast pointer type to usize (GCC does not support pointer SIMD vectors). - let scalar = args[0].immediate(); - let scalar = if scalar.get_type().unqualified() != elem_ty.unqualified() { - bx.ptrtoint(scalar, elem_ty) + let value = args[0]; + let scalar = if value.layout.ty.is_numeric() { + value.immediate() + } else if value.layout.ty.is_raw_ptr() { + bx.ptrtoint(value.immediate(), elem_ty) } else { - scalar + return_error!(InvalidMonomorphization::UnsupportedOperation { + span, + name, + in_ty: ret_ty, + in_elem: value.layout.ty + }); }; let elements = vec![scalar; out_len as usize]; From c591bc103afb0c6cb8ee90729e1cef8e1be91cab Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 9 Jan 2026 15:47:02 +0100 Subject: [PATCH 107/273] Remove all allows for `diagnostic_outside_of_impl` and `untranslatable_diagnostic` throughout the codebase This PR was mostly made by search&replacing --- clippy_config/src/lib.rs | 2 -- clippy_lints/src/lib.rs | 2 -- clippy_lints_internal/src/lib.rs | 2 -- clippy_utils/src/lib.rs | 2 -- src/driver.rs | 2 -- 5 files changed, 10 deletions(-) diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index 67904b4fcdc8..a565a21a0e77 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -9,8 +9,6 @@ #![allow( clippy::must_use_candidate, clippy::missing_panics_doc, - rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic )] #![deny(clippy::derive_deserialize_allowing_unknown)] diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a957afdb1910..ae236ac25de7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -14,8 +14,6 @@ #![allow( clippy::missing_docs_in_private_items, clippy::must_use_candidate, - rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic, clippy::literal_string_with_formatting_args )] #![warn( diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index cca5608fa6be..e009f263f11f 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -3,8 +3,6 @@ clippy::missing_docs_in_private_items, clippy::must_use_candidate, clippy::symbol_as_str, - rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic )] #![warn( trivial_casts, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 2a620e917228..9b6a9937b8ac 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -11,8 +11,6 @@ clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate, - rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic )] #![warn( trivial_casts, diff --git a/src/driver.rs b/src/driver.rs index 8693973ef78c..c9ca6335de6e 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -1,5 +1,3 @@ -#![allow(rustc::diagnostic_outside_of_impl)] -#![allow(rustc::untranslatable_diagnostic)] #![feature(rustc_private)] // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] From 9aaa581fe8ad8517f28e5cebc4f240eebbea23b0 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Mon, 19 Jan 2026 20:42:24 +0100 Subject: [PATCH 108/273] rustc-dev-guide: Mention `--extern` modifiers for `aux-crate` directive --- src/doc/rustc-dev-guide/src/tests/compiletest.md | 4 +++- src/doc/rustc-dev-guide/src/tests/directives.md | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index d69e0a5ce988..b212675d3fcf 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -655,7 +655,9 @@ to link to the extern crate to make the crate be available as an extern prelude. That allows you to specify the additional syntax of the `--extern` flag, such as renaming a dependency. For example, `//@ aux-crate:foo=bar.rs` will compile `auxiliary/bar.rs` and make it available under then name `foo` within the test. -This is similar to how Cargo does dependency renaming. +This is similar to how Cargo does dependency renaming. It is also possible to +specify [`--extern` modifiers](https://github.com/rust-lang/rust/issues/98405). +For example, `//@ aux-crate:noprelude:foo=bar.rs`. `aux-bin` is similar to `aux-build` but will build a binary instead of a library. The binary will be available in `auxiliary/bin` relative to the working diff --git a/src/doc/rustc-dev-guide/src/tests/directives.md b/src/doc/rustc-dev-guide/src/tests/directives.md index ae341599f15a..81c421bc92c4 100644 --- a/src/doc/rustc-dev-guide/src/tests/directives.md +++ b/src/doc/rustc-dev-guide/src/tests/directives.md @@ -53,14 +53,14 @@ Directives can generally be found by browsing the See [Building auxiliary crates](compiletest.html#building-auxiliary-crates) -| Directive | Explanation | Supported test suites | Possible values | -|-----------------------|-------------------------------------------------------------------------------------------------------|----------------------------------------|-----------------------------------------------| -| `aux-bin` | Build a aux binary, made available in `auxiliary/bin` relative to test directory | All except `run-make`/`run-make-cargo` | Path to auxiliary `.rs` file | -| `aux-build` | Build a separate crate from the named source file | All except `run-make`/`run-make-cargo` | Path to auxiliary `.rs` file | -| `aux-crate` | Like `aux-build` but makes available as extern prelude | All except `run-make`/`run-make-cargo` | `=` | -| `aux-codegen-backend` | Similar to `aux-build` but pass the compiled dylib to `-Zcodegen-backend` when building the main file | `ui-fulldeps` | Path to codegen backend file | -| `proc-macro` | Similar to `aux-build`, but for aux forces host and don't use `-Cprefer-dynamic`[^pm]. | All except `run-make`/`run-make-cargo` | Path to auxiliary proc-macro `.rs` file | -| `build-aux-docs` | Build docs for auxiliaries as well. Note that this only works with `aux-build`, not `aux-crate`. | All except `run-make`/`run-make-cargo` | N/A | +| Directive | Explanation | Supported test suites | Possible values | +|-----------------------|-------------------------------------------------------------------------------------------------------|----------------------------------------|--------------------------------------------------------------------| +| `aux-bin` | Build a aux binary, made available in `auxiliary/bin` relative to test directory | All except `run-make`/`run-make-cargo` | Path to auxiliary `.rs` file | +| `aux-build` | Build a separate crate from the named source file | All except `run-make`/`run-make-cargo` | Path to auxiliary `.rs` file | +| `aux-crate` | Like `aux-build` but makes available as extern prelude | All except `run-make`/`run-make-cargo` | `[:]=` | +| `aux-codegen-backend` | Similar to `aux-build` but pass the compiled dylib to `-Zcodegen-backend` when building the main file | `ui-fulldeps` | Path to codegen backend file | +| `proc-macro` | Similar to `aux-build`, but for aux forces host and don't use `-Cprefer-dynamic`[^pm]. | All except `run-make`/`run-make-cargo` | Path to auxiliary proc-macro `.rs` file | +| `build-aux-docs` | Build docs for auxiliaries as well. Note that this only works with `aux-build`, not `aux-crate`. | All except `run-make`/`run-make-cargo` | N/A | [^pm]: please see the [Auxiliary proc-macro section](compiletest.html#auxiliary-proc-macro) in the compiletest chapter for specifics. From 28eca4a8fe72ba498761e567ed92fa2a61c5e726 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 19 Jan 2026 21:50:15 +0100 Subject: [PATCH 109/273] Only run finalizers of accepted attributes --- compiler/rustc_attr_parsing/src/context.rs | 22 +++++++++----------- compiler/rustc_attr_parsing/src/interface.rs | 8 +++++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 0c882fee01c8..693ee0391171 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -103,18 +103,18 @@ type GroupType = LazyLock>; pub(super) struct GroupTypeInner { pub(super) accepters: BTreeMap<&'static [Symbol], Vec>>, - pub(super) finalizers: Vec>, } pub(super) struct GroupTypeInnerAccept { pub(super) template: AttributeTemplate, pub(super) accept_fn: AcceptFn, pub(super) allowed_targets: AllowedTargets, + pub(super) finalizer: FinalizeFn, } -type AcceptFn = +pub(crate) type AcceptFn = Box Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser) + Send + Sync>; -type FinalizeFn = +pub(crate) type FinalizeFn = Box) -> Option>; macro_rules! attribute_parsers { @@ -142,8 +142,7 @@ macro_rules! attribute_parsers { @[$stage: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?]; ) => { pub(crate) static $name: GroupType<$stage> = LazyLock::new(|| { - let mut accepts = BTreeMap::<_, Vec>>::new(); - let mut finalizes = Vec::>::new(); + let mut accepters = BTreeMap::<_, Vec>>::new(); $( { thread_local! { @@ -151,7 +150,7 @@ macro_rules! attribute_parsers { }; for (path, template, accept_fn) in <$names>::ATTRIBUTES { - accepts.entry(*path).or_default().push(GroupTypeInnerAccept { + accepters.entry(*path).or_default().push(GroupTypeInnerAccept { template: *template, accept_fn: Box::new(|cx, args| { STATE_OBJECT.with_borrow_mut(|s| { @@ -159,17 +158,16 @@ macro_rules! attribute_parsers { }) }), allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS, + finalizer: Box::new(|cx| { + let state = STATE_OBJECT.take(); + state.finalize(cx) + }), }); } - - finalizes.push(Box::new(|cx| { - let state = STATE_OBJECT.take(); - state.finalize(cx) - })); } )* - GroupTypeInner { accepters:accepts, finalizers:finalizes } + GroupTypeInner { accepters } }); }; } diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index c6be18321b5e..bac4936c20d2 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -12,7 +12,7 @@ use rustc_session::Session; use rustc_session::lint::{BuiltinLintDiag, LintId}; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; -use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage}; +use crate::context::{AcceptContext, FinalizeContext, FinalizeFn, SharedContext, Stage}; use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState}; use crate::parser::{ArgParser, PathParser, RefPathParser}; use crate::session_diagnostics::ParsedDescription; @@ -270,6 +270,8 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { let mut attr_paths: Vec> = Vec::new(); let mut early_parsed_state = EarlyParsedState::default(); + let mut finalizers: Vec<&FinalizeFn> = Vec::with_capacity(attrs.len()); + for attr in attrs { // If we're only looking for a single attribute, skip all the ones we don't care about. if let Some(expected) = self.parse_only { @@ -383,6 +385,8 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { }; (accept.accept_fn)(&mut cx, &args); + finalizers.push(&accept.finalizer); + if !matches!(cx.stage.should_emit(), ShouldEmit::Nothing) { Self::check_target(&accept.allowed_targets, target, &mut cx); } @@ -417,7 +421,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { } early_parsed_state.finalize_early_parsed_attributes(&mut attributes); - for f in &S::parsers().finalizers { + for f in &finalizers { if let Some(attr) = f(&mut FinalizeContext { shared: SharedContext { cx: self, target_span, target, emit_lint: &mut emit_lint }, all_attrs: &attr_paths, From 6dc27bf7de8dd306baba6e6904fa5234bc56d9f1 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 19 Jan 2026 21:50:29 +0100 Subject: [PATCH 110/273] Update uitests --- tests/pretty/delegation-inherit-attributes.pp | 2 +- tests/pretty/delegation-inline-attribute.pp | 20 ++++++------- ...ute-rejects-already-stable-features.stderr | 28 +++++++++---------- tests/ui/loop-match/invalid-attribute.stderr | 16 +++++------ 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/pretty/delegation-inherit-attributes.pp b/tests/pretty/delegation-inherit-attributes.pp index 2398cae90fdb..9d51a80da7b9 100644 --- a/tests/pretty/delegation-inherit-attributes.pp +++ b/tests/pretty/delegation-inherit-attributes.pp @@ -21,8 +21,8 @@ mod to_reuse { #[attr = Cold] fn foo_no_reason(x: usize) -> usize { x } - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] #[attr = Cold] + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] fn bar(x: usize) -> usize { x } } diff --git a/tests/pretty/delegation-inline-attribute.pp b/tests/pretty/delegation-inline-attribute.pp index 5235fd8d0ef2..f83ae47b81f6 100644 --- a/tests/pretty/delegation-inline-attribute.pp +++ b/tests/pretty/delegation-inline-attribute.pp @@ -44,9 +44,9 @@ impl Trait for S { fn foo0(arg0: _) -> _ { to_reuse::foo(self + 1) } // Check that #[inline(hint)] is added when other attributes present in inner reuse - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] - #[attr = MustUse] #[attr = Cold] + #[attr = MustUse] + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] #[attr = Inline(Hint)] fn foo1(arg0: _) -> _ { to_reuse::foo(self / 2) } @@ -59,18 +59,18 @@ impl Trait for S { fn foo3(arg0: _) -> _ { to_reuse::foo(self / 2) } // Check that #[inline(never)] is preserved when there are other attributes in inner reuse - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] - #[attr = Inline(Never)] - #[attr = MustUse] #[attr = Cold] + #[attr = MustUse] + #[attr = Inline(Never)] + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] fn foo4(arg0: _) -> _ { to_reuse::foo(self / 2) } }.foo() } // Check that #[inline(hint)] is added when there are other attributes present in trait reuse - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] - #[attr = MustUse] #[attr = Cold] + #[attr = MustUse] + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] #[attr = Inline(Hint)] fn foo1(self: _) -> _ { self.0.foo1() } @@ -83,10 +83,10 @@ impl Trait for S { fn foo3(self: _) -> _ { self.0.foo3() } // Check that #[inline(never)] is preserved when there are other attributes in trait reuse - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] - #[attr = Inline(Never)] - #[attr = MustUse] #[attr = Cold] + #[attr = MustUse] + #[attr = Inline(Never)] + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] fn foo4(self: _) -> _ { self.0.foo4() } } diff --git a/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr b/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr index d599523c7274..319056a9c889 100644 --- a/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr +++ b/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr @@ -1,17 +1,3 @@ -error: can't mark as unstable using an already stable feature - --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1 - | -LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable -LL | const fn my_fun() {} - | -------------------- the stability attribute annotates this item - | -help: consider removing the attribute - --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1 - | -LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - error: can't mark as unstable using an already stable feature --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1 | @@ -27,5 +13,19 @@ help: consider removing the attribute LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error: can't mark as unstable using an already stable feature + --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1 + | +LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable +LL | const fn my_fun() {} + | -------------------- the stability attribute annotates this item + | +help: consider removing the attribute + --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1 + | +LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + error: aborting due to 2 previous errors diff --git a/tests/ui/loop-match/invalid-attribute.stderr b/tests/ui/loop-match/invalid-attribute.stderr index ddb68aea31b6..c23452b9b844 100644 --- a/tests/ui/loop-match/invalid-attribute.stderr +++ b/tests/ui/loop-match/invalid-attribute.stderr @@ -94,14 +94,6 @@ LL | #[const_continue] | = help: `#[const_continue]` can be applied to -error: `#[const_continue]` should be applied to a break expression - --> $DIR/invalid-attribute.rs:40:9 - | -LL | #[const_continue] - | ^^^^^^^^^^^^^^^^^ -LL | 5 - | - not a break expression - error: `#[loop_match]` should be applied to a loop --> $DIR/invalid-attribute.rs:39:9 | @@ -111,5 +103,13 @@ LL | #[const_continue] LL | 5 | - not a loop +error: `#[const_continue]` should be applied to a break expression + --> $DIR/invalid-attribute.rs:40:9 + | +LL | #[const_continue] + | ^^^^^^^^^^^^^^^^^ +LL | 5 + | - not a break expression + error: aborting due to 14 previous errors From 566c23de41e5be622ef935d25ab9aceec59cb13b Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 11 Jan 2026 22:13:02 +1100 Subject: [PATCH 111/273] THIR patterns: Explicitly distinguish `&pin` from plain `&`/`&mut` --- compiler/rustc_middle/src/thir.rs | 14 ++++++++++++-- compiler/rustc_middle/src/thir/visit.rs | 2 +- .../src/builder/matches/match_pair.rs | 18 ++++++++++-------- .../src/builder/matches/mod.rs | 8 ++++++-- .../src/thir/pattern/const_to_pat.rs | 6 +++++- .../rustc_mir_build/src/thir/pattern/mod.rs | 12 ++++++++---- compiler/rustc_mir_build/src/thir/print.rs | 3 ++- compiler/rustc_pattern_analysis/src/lib.rs | 1 + compiler/rustc_pattern_analysis/src/rustc.rs | 10 +++++----- .../ui/pin-ergonomics/user-type-projection.rs | 19 +++++++++++++++++++ tests/ui/thir-print/str-patterns.stdout | 2 ++ 11 files changed, 71 insertions(+), 24 deletions(-) create mode 100644 tests/ui/pin-ergonomics/user-type-projection.rs diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index dcbed92d350b..5e8031403565 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -807,12 +807,22 @@ pub enum PatKind<'tcx> { subpatterns: Vec>, }, - /// `box P`, `&P`, `&mut P`, etc. + /// Explicit or implicit `&P` or `&mut P`, for some subpattern `P`. + /// + /// Implicit `&`/`&mut` patterns can be inserted by match-ergonomics. + /// + /// With `feature(pin_ergonomics)`, this can also be `&pin const P` or + /// `&pin mut P`, as indicated by the `pin` field. Deref { + #[type_visitable(ignore)] + pin: hir::Pinnedness, subpattern: Box>, }, - /// Deref pattern, written `box P` for now. + /// Explicit or implicit `deref!(..)` pattern, under `feature(deref_patterns)`. + /// Represents a call to `Deref` or `DerefMut`, or a deref-move of `Box`. + /// + /// `box P` patterns also lower to this, under `feature(box_patterns)`. DerefPattern { subpattern: Box>, /// Whether the pattern scrutinee needs to be borrowed in order to call `Deref::deref` or diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index c06d5c5f0ebb..8e0e3aa294b8 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -268,7 +268,7 @@ pub(crate) fn for_each_immediate_subpat<'a, 'tcx>( | PatKind::Error(_) => {} PatKind::Binding { subpattern: Some(subpattern), .. } - | PatKind::Deref { subpattern } + | PatKind::Deref { subpattern, .. } | PatKind::DerefPattern { subpattern, .. } => callback(subpattern), PatKind::Variant { subpatterns, .. } | PatKind::Leaf { subpatterns } => { diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index e80e29415e6f..a3662b2768cd 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use rustc_abi::FieldIdx; use rustc_middle::mir::*; +use rustc_middle::span_bug; use rustc_middle::thir::*; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -314,23 +315,24 @@ impl<'tcx> MatchPairTree<'tcx> { None } - // FIXME: Pin-patterns should probably have their own pattern kind, - // instead of overloading `PatKind::Deref` via the pattern type. - PatKind::Deref { ref subpattern } - if let Some(ref_ty) = pattern.ty.pinned_ty() - && ref_ty.is_ref() => - { + PatKind::Deref { pin: Pinnedness::Pinned, ref subpattern } => { + let pinned_ref_ty = match pattern.ty.pinned_ty() { + Some(p_ty) if p_ty.is_ref() => p_ty, + _ => span_bug!(pattern.span, "bad type for pinned deref: {:?}", pattern.ty), + }; MatchPairTree::for_pattern( - place_builder.field(FieldIdx::ZERO, ref_ty).deref(), + // Project into the `Pin(_)` struct, then deref the inner `&` or `&mut`. + place_builder.field(FieldIdx::ZERO, pinned_ref_ty).deref(), subpattern, cx, &mut subpairs, extra_data, ); + None } - PatKind::Deref { ref subpattern } + PatKind::Deref { pin: Pinnedness::Not, ref subpattern } | PatKind::DerefPattern { ref subpattern, borrow: DerefPatBorrowMode::Box } => { MatchPairTree::for_pattern( place_builder.deref(), diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 2f9486c2d552..d04322c320b4 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -10,7 +10,7 @@ use std::mem; use std::sync::Arc; use itertools::{Itertools, Position}; -use rustc_abi::{FIRST_VARIANT, VariantIdx}; +use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx}; use rustc_data_structures::debug_assert_matches; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -909,7 +909,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | PatKind::Never | PatKind::Error(_) => {} - PatKind::Deref { ref subpattern } => { + PatKind::Deref { pin: Pinnedness::Pinned, ref subpattern } => { + // Project into the `Pin(_)` struct, then deref the inner `&` or `&mut`. + visit_subpat(self, subpattern, &user_tys.leaf(FieldIdx::ZERO).deref(), f); + } + PatKind::Deref { pin: Pinnedness::Not, ref subpattern } => { visit_subpat(self, subpattern, &user_tys.deref(), f); } diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 7fbf8cd11466..0a0e0d06061e 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -294,8 +294,12 @@ impl<'tcx> ConstToPat<'tcx> { || pointee_ty.is_slice() || pointee_ty.is_sized(tcx, self.typing_env) { - // References have the same valtree representation as their pointee. PatKind::Deref { + // This node has type `ty::Ref`, so it's not a pin-deref. + pin: hir::Pinnedness::Not, + // Lower the valtree to a pattern as the pointee type. + // This works because references have the same valtree + // representation as their pointee. subpattern: self.valtree_to_pat(ty::Value { ty: *pointee_ty, valtree }), } } else { diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index d0abb6396145..3641561567bc 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -132,12 +132,16 @@ impl<'tcx> PatCtxt<'tcx> { debug!("{:?}: wrapping pattern with adjustment {:?}", thir_pat, adjust); let span = thir_pat.span; let kind = match adjust.kind { - PatAdjust::BuiltinDeref => PatKind::Deref { subpattern: thir_pat }, + PatAdjust::BuiltinDeref => { + PatKind::Deref { pin: hir::Pinnedness::Not, subpattern: thir_pat } + } PatAdjust::OverloadedDeref => { let borrow = self.typeck_results.deref_pat_borrow_mode(adjust.source, pat); PatKind::DerefPattern { subpattern: thir_pat, borrow } } - PatAdjust::PinDeref => PatKind::Deref { subpattern: thir_pat }, + PatAdjust::PinDeref => { + PatKind::Deref { pin: hir::Pinnedness::Pinned, subpattern: thir_pat } + } }; Box::new(Pat { span, ty: adjust.source, kind, extra: None }) }); @@ -334,7 +338,7 @@ impl<'tcx> PatCtxt<'tcx> { let borrow = self.typeck_results.deref_pat_borrow_mode(ty, subpattern); PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), borrow } } - hir::PatKind::Ref(subpattern, _, _) => { + hir::PatKind::Ref(subpattern, pin, _) => { // Track the default binding mode for the Rust 2024 migration suggestion. let opt_old_mode_span = self.rust_2024_migration.as_mut().and_then(|s| s.visit_explicit_deref()); @@ -342,7 +346,7 @@ impl<'tcx> PatCtxt<'tcx> { if let Some(s) = &mut self.rust_2024_migration { s.leave_ref(opt_old_mode_span); } - PatKind::Deref { subpattern } + PatKind::Deref { pin, subpattern } } hir::PatKind::Box(subpattern) => PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index a87257fa79bf..db8b2518981d 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -774,8 +774,9 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, "]", depth_lvl + 2); print_indented!(self, "}", depth_lvl + 1); } - PatKind::Deref { subpattern } => { + PatKind::Deref { pin, subpattern } => { print_indented!(self, "Deref { ", depth_lvl + 1); + print_indented!(self, format_args!("pin: {pin:?}"), depth_lvl + 2); print_indented!(self, "subpattern:", depth_lvl + 2); self.print_pat(subpattern, depth_lvl + 2); print_indented!(self, "}", depth_lvl + 1); diff --git a/compiler/rustc_pattern_analysis/src/lib.rs b/compiler/rustc_pattern_analysis/src/lib.rs index 364ffc7bace0..265284086377 100644 --- a/compiler/rustc_pattern_analysis/src/lib.rs +++ b/compiler/rustc_pattern_analysis/src/lib.rs @@ -4,6 +4,7 @@ // tidy-alphabetical-start #![allow(unused_crate_dependencies)] +#![cfg_attr(feature = "rustc", feature(if_let_guard))] // tidy-alphabetical-end pub(crate) mod checks; diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs index 5e75192ff309..3e97842b7f39 100644 --- a/compiler/rustc_pattern_analysis/src/rustc.rs +++ b/compiler/rustc_pattern_analysis/src/rustc.rs @@ -4,8 +4,8 @@ use std::iter::once; use rustc_abi::{FIRST_VARIANT, FieldIdx, Integer, VariantIdx}; use rustc_arena::DroplessArena; -use rustc_hir::HirId; use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, HirId}; use rustc_index::{Idx, IndexVec}; use rustc_middle::middle::stability::EvalResult; use rustc_middle::thir::{self, Pat, PatKind, PatRange, PatRangeBoundary}; @@ -468,12 +468,12 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { fields = vec![]; arity = 0; } - PatKind::Deref { subpattern } => { + PatKind::Deref { pin, subpattern } => { fields = vec![self.lower_pat(subpattern).at_index(0)]; arity = 1; - ctor = match ty.pinned_ref() { - None if ty.is_ref() => Ref, - Some((inner_ty, _)) => { + ctor = match pin { + hir::Pinnedness::Not if ty.is_ref() => Ref, + hir::Pinnedness::Pinned if let Some((inner_ty, _)) = ty.pinned_ref() => { self.internal_state.has_lowered_deref_pat.set(true); DerefPattern(RevealedTy(inner_ty)) } diff --git a/tests/ui/pin-ergonomics/user-type-projection.rs b/tests/ui/pin-ergonomics/user-type-projection.rs new file mode 100644 index 000000000000..f482586b6ebc --- /dev/null +++ b/tests/ui/pin-ergonomics/user-type-projection.rs @@ -0,0 +1,19 @@ +#![crate_type = "rlib"] +#![feature(pin_ergonomics)] +#![expect(incomplete_features)] +//@ edition: 2024 +//@ check-pass + +// Test that we don't ICE when projecting user-type-annotations through a `&pin` pattern. +// +// Historically, this could occur when the code handling those projections did not know +// about `&pin` patterns, and incorrectly treated them as plain `&`/`&mut` patterns instead. + +struct Data { + x: u32 +} + +pub fn project_user_type_through_pin() -> u32 { + let &pin const Data { x }: &pin const Data = &pin const Data { x: 30 }; + x +} diff --git a/tests/ui/thir-print/str-patterns.stdout b/tests/ui/thir-print/str-patterns.stdout index 6941ab15130f..09212e7d68ae 100644 --- a/tests/ui/thir-print/str-patterns.stdout +++ b/tests/ui/thir-print/str-patterns.stdout @@ -10,6 +10,7 @@ Thir { span: $DIR/str-patterns.rs:11:9: 11:16 (#0), extra: None, kind: Deref { + pin: Not, subpattern: Pat { ty: str, span: $DIR/str-patterns.rs:11:9: 11:16 (#0), @@ -50,6 +51,7 @@ Thir { }, ), kind: Deref { + pin: Not, subpattern: Pat { ty: str, span: $DIR/str-patterns.rs:12:9: 12:17 (#0), From 2b0f119a8e64fbc115c43bf1176f72fddd1ec5be Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 20 Jan 2026 05:04:00 +0000 Subject: [PATCH 112/273] Prepare for merging from rust-lang/rust This updates the rust-version file to 63f4513795b198d034f5d19962659ea488163755. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a6ccd9bab393..f805775e2768 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -b6fdaf2a15736cbccf248b532f48e33179614d40 +63f4513795b198d034f5d19962659ea488163755 From ff5a250132023eb97dd0e01cc15ba43a13e38045 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Tue, 20 Jan 2026 05:13:00 +0000 Subject: [PATCH 113/273] fmt --- src/tools/miri/src/bin/miri.rs | 2 +- src/tools/miri/src/shims/time.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 48f8e146a190..b28d70f05882 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -3,7 +3,7 @@ clippy::manual_range_contains, clippy::useless_format, clippy::field_reassign_with_default, - clippy::needless_lifetimes, + clippy::needless_lifetimes )] // The rustc crates we need diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 5352b08d8d25..c49aa9f20f6c 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -347,7 +347,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert_eq!(unblock, UnblockKind::TimedOut); interp_ok(()) } - ) + ), ); interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS From 24e28e71f803cb429697ac5577ba28a294d09c6c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 20 Jan 2026 08:36:00 +0100 Subject: [PATCH 114/273] make comment consistent --- src/tools/miri/src/shims/unix/macos/foreign_items.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index be4f2e66bab1..e968e7f4d628 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -122,8 +122,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } - // FIXME: add a test that directly calls this function. "mach_wait_until" => { + // FIXME: This does not have a direct test (#3179). let [deadline] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.mach_wait_until(deadline)?; this.write_scalar(result, dest)?; From e3f198ec05bdae302caf792cb4ab2e07d834d7d9 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Tue, 20 Jan 2026 16:18:56 +0800 Subject: [PATCH 115/273] LoongArch: Fix direct-access-external-data test On LoongArch targets, `-Cdirect-access-external-data` defaults to `no`. Since copy relocations are not supported, `dso_local` is not emitted under `-Crelocation-model=static`, unlike on other targets. --- .../direct-access-external-data.rs | 1 + .../loongarch/direct-access-external-data.rs | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/codegen-llvm/loongarch/direct-access-external-data.rs diff --git a/tests/codegen-llvm/direct-access-external-data.rs b/tests/codegen-llvm/direct-access-external-data.rs index 73dc08dc2b57..a151bb6012e1 100644 --- a/tests/codegen-llvm/direct-access-external-data.rs +++ b/tests/codegen-llvm/direct-access-external-data.rs @@ -1,3 +1,4 @@ +//@ ignore-loongarch64 (handles dso_local differently) //@ ignore-powerpc64 (handles dso_local differently) //@ ignore-apple (handles dso_local differently) diff --git a/tests/codegen-llvm/loongarch/direct-access-external-data.rs b/tests/codegen-llvm/loongarch/direct-access-external-data.rs new file mode 100644 index 000000000000..de495d7fe9a7 --- /dev/null +++ b/tests/codegen-llvm/loongarch/direct-access-external-data.rs @@ -0,0 +1,47 @@ +//@ only-loongarch64 + +//@ revisions: DEFAULT PIE DIRECT INDIRECT +//@ [DEFAULT] compile-flags: -C relocation-model=static +//@ [PIE] compile-flags: -C relocation-model=pie +//@ [DIRECT] compile-flags: -C relocation-model=pie -Z direct-access-external-data=yes +//@ [INDIRECT] compile-flags: -C relocation-model=static -Z direct-access-external-data=no + +#![crate_type = "rlib"] +#![feature(linkage)] + +unsafe extern "C" { + // CHECK: @VAR = external + // DEFAULT-NOT: dso_local + // PIE-NOT: dso_local + // DIRECT-SAME: dso_local + // INDIRECT-NOT: dso_local + // CHECK-SAME: global i32 + safe static VAR: i32; + + // When "linkage" is used, we generate an indirection global. + // Check dso_local is still applied to the actual global. + // CHECK: @EXTERNAL = external + // DEFAULT-NOT: dso_local + // PIE-NOT: dso_local + // DIRECT-SAME: dso_local + // INDIRECT-NOT: dso_local + // CHECK-SAME: global i8 + #[linkage = "external"] + safe static EXTERNAL: *const u32; + + // CHECK: @WEAK = extern_weak + // DEFAULT-NOT: dso_local + // PIE-NOT: dso_local + // DIRECT-SAME: dso_local + // INDIRECT-NOT: dso_local + // CHECK-SAME: global i8 + #[linkage = "extern_weak"] + safe static WEAK: *const u32; +} + +#[no_mangle] +pub fn refer() { + core::hint::black_box(VAR); + core::hint::black_box(EXTERNAL); + core::hint::black_box(WEAK); +} From b65e1fdcb8f85ccb91a99b81e8c4f99d9c8ee643 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 18 Jan 2026 22:21:51 +0100 Subject: [PATCH 116/273] Port `#[patchable_function_entry]` to attr parser --- .../src/attributes/codegen_attrs.rs | 97 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 17 +++- .../src/session_diagnostics.rs | 15 +++ compiler/rustc_codegen_ssa/messages.ftl | 11 --- .../rustc_codegen_ssa/src/codegen_attrs.rs | 61 +----------- compiler/rustc_codegen_ssa/src/errors.rs | 33 ------- .../rustc_hir/src/attrs/data_structures.rs | 3 + .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + .../rustc_hir/src/attrs/pretty_printing.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 2 +- tests/ui/attributes/malformed-attrs.stderr | 15 +-- .../patchable-function-entry-attribute.rs | 19 +++- .../patchable-function-entry-attribute.stderr | 64 ++++++++---- 13 files changed, 205 insertions(+), 135 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 5bbaeda18df3..063fa12d3896 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -717,3 +717,100 @@ impl NoArgsAttributeParser for EiiForeignItemParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::EiiForeignItem; } + +pub(crate) struct PatchableFunctionEntryParser; + +impl SingleAttributeParser for PatchableFunctionEntryParser { + const PATH: &[Symbol] = &[sym::patchable_function_entry]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); + const TEMPLATE: AttributeTemplate = template!(List: &["prefix_nops = m, entry_nops = n"]); + + fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { + let Some(meta_item_list) = args.list() else { + cx.expected_list(cx.attr_span, args); + return None; + }; + + let mut prefix = None; + let mut entry = None; + + if meta_item_list.len() == 0 { + cx.expected_list(meta_item_list.span, args); + return None; + } + + let mut errored = false; + + for item in meta_item_list.mixed() { + let Some(meta_item) = item.meta_item() else { + errored = true; + cx.expected_name_value(item.span(), None); + continue; + }; + + let Some(name_value_lit) = meta_item.args().name_value() else { + errored = true; + cx.expected_name_value(item.span(), None); + continue; + }; + + let attrib_to_write = match meta_item.ident().map(|ident| ident.name) { + Some(sym::prefix_nops) => { + // Duplicate prefixes are not allowed + if prefix.is_some() { + errored = true; + cx.duplicate_key(meta_item.path().span(), sym::prefix_nops); + continue; + } + &mut prefix + } + Some(sym::entry_nops) => { + // Duplicate entries are not allowed + if entry.is_some() { + errored = true; + cx.duplicate_key(meta_item.path().span(), sym::entry_nops); + continue; + } + &mut entry + } + _ => { + errored = true; + cx.expected_specific_argument( + meta_item.path().span(), + &[sym::prefix_nops, sym::entry_nops], + ); + continue; + } + }; + + let rustc_ast::LitKind::Int(val, _) = name_value_lit.value_as_lit().kind else { + errored = true; + cx.expected_integer_literal(name_value_lit.value_span); + continue; + }; + + let Ok(val) = val.get().try_into() else { + errored = true; + cx.expected_integer_literal_in_range( + name_value_lit.value_span, + u8::MIN as isize, + u8::MAX as isize, + ); + continue; + }; + + *attrib_to_write = Some(val); + } + + if errored { + None + } else { + Some(AttributeKind::PatchableFunctionEntry { + prefix: prefix.unwrap_or(0), + entry: entry.unwrap_or(0), + }) + } + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 0c882fee01c8..7bcc1f3448e5 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -23,8 +23,8 @@ use crate::attributes::cfi_encoding::CfiEncodingParser; use crate::attributes::codegen_attrs::{ ColdParser, CoverageParser, EiiForeignItemParser, ExportNameParser, ForceTargetFeatureParser, NakedParser, NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser, - RustcPassIndirectlyInNonRusticAbisParser, SanitizeParser, TargetFeatureParser, - ThreadLocalParser, TrackCallerParser, UsedParser, + PatchableFunctionEntryParser, RustcPassIndirectlyInNonRusticAbisParser, SanitizeParser, + TargetFeatureParser, ThreadLocalParser, TrackCallerParser, UsedParser, }; use crate::attributes::confusables::ConfusablesParser; use crate::attributes::crate_level::{ @@ -223,6 +223,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, @@ -504,6 +505,18 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { self.emit_parse_error(span, AttributeParseErrorReason::ExpectedIntegerLiteral) } + pub(crate) fn expected_integer_literal_in_range( + &self, + span: Span, + lower_bound: isize, + upper_bound: isize, + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedIntegerLiteralInRange { lower_bound, upper_bound }, + ) + } + pub(crate) fn expected_list(&self, span: Span, args: &ArgParser) -> ErrorGuaranteed { let span = match args { ArgParser::NoArgs => span, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 85e7891b1e64..f9748542beb9 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -525,6 +525,10 @@ pub(crate) enum AttributeParseErrorReason<'a> { byte_string: Option, }, ExpectedIntegerLiteral, + ExpectedIntegerLiteralInRange { + lower_bound: isize, + upper_bound: isize, + }, ExpectedAtLeastOneArgument, ExpectedSingleArgument, ExpectedList, @@ -596,6 +600,17 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { AttributeParseErrorReason::ExpectedIntegerLiteral => { diag.span_label(self.span, "expected an integer literal here"); } + AttributeParseErrorReason::ExpectedIntegerLiteralInRange { + lower_bound, + upper_bound, + } => { + diag.span_label( + self.span, + format!( + "expected an integer literal in the range of {lower_bound}..={upper_bound}" + ), + ); + } AttributeParseErrorReason::ExpectedSingleArgument => { diag.span_label(self.span, "expected a single argument here"); diag.code(E0805); diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 4875f309cca5..a49f411a7df6 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -48,8 +48,6 @@ codegen_ssa_error_creating_remark_dir = failed to create remark directory: {$err codegen_ssa_error_writing_def_file = error writing .DEF file: {$error} -codegen_ssa_expected_name_value_pair = expected name value pair - codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified codegen_ssa_extract_bundled_libs_archive_member = failed to get data from archive member '{$rlib}': {$error} @@ -90,9 +88,6 @@ codegen_ssa_incorrect_cgu_reuse_type = codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient. -codegen_ssa_invalid_literal_value = invalid literal value - .label = value must be an integer between `0` and `255` - codegen_ssa_invalid_monomorphization_basic_float_type = invalid monomorphization of `{$name}` intrinsic: expected basic float type, found `{$ty}` codegen_ssa_invalid_monomorphization_basic_integer_or_ptr_type = invalid monomorphization of `{$name}` intrinsic: expected basic integer or pointer type, found `{$ty}` @@ -225,9 +220,6 @@ codegen_ssa_no_natvis_directory = error enumerating natvis directory: {$error} codegen_ssa_no_saved_object_file = cached cgu {$cgu_name} should have an object file, but doesn't -codegen_ssa_out_of_range_integer = integer value out of range - .label = value must be between `0` and `255` - codegen_ssa_processing_dymutil_failed = processing debug info with `dsymutil` failed: {$status} .note = {$output} @@ -357,9 +349,6 @@ codegen_ssa_unable_to_run_dsymutil = unable to run `dsymutil`: {$error} codegen_ssa_unable_to_write_debugger_visualizer = unable to write debugger visualizer file `{$path}`: {$error} -codegen_ssa_unexpected_parameter_name = unexpected parameter name - .label = expected `{$prefix_nops}` or `{$entry_nops}` - codegen_ssa_unknown_archive_kind = don't know how to build archive of type: {$kind} diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index e35d884b6711..296c96ce4ae6 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -47,59 +47,6 @@ fn try_fn_sig<'tcx>( } } -// FIXME(jdonszelmann): remove when patchable_function_entry becomes a parsed attr -fn parse_patchable_function_entry( - tcx: TyCtxt<'_>, - attr: &Attribute, -) -> Option { - attr.meta_item_list().and_then(|l| { - let mut prefix = None; - let mut entry = None; - for item in l { - let Some(meta_item) = item.meta_item() else { - tcx.dcx().emit_err(errors::ExpectedNameValuePair { span: item.span() }); - continue; - }; - - let Some(name_value_lit) = meta_item.name_value_literal() else { - tcx.dcx().emit_err(errors::ExpectedNameValuePair { span: item.span() }); - continue; - }; - - let attrib_to_write = match meta_item.name() { - Some(sym::prefix_nops) => &mut prefix, - Some(sym::entry_nops) => &mut entry, - _ => { - tcx.dcx().emit_err(errors::UnexpectedParameterName { - span: item.span(), - prefix_nops: sym::prefix_nops, - entry_nops: sym::entry_nops, - }); - continue; - } - }; - - let rustc_ast::LitKind::Int(val, _) = name_value_lit.kind else { - tcx.dcx().emit_err(errors::InvalidLiteralValue { span: name_value_lit.span }); - continue; - }; - - let Ok(val) = val.get().try_into() else { - tcx.dcx().emit_err(errors::OutOfRangeInteger { span: name_value_lit.span }); - continue; - }; - - *attrib_to_write = Some(val); - } - - if let (None, None) = (prefix, entry) { - tcx.dcx().span_err(attr.span(), "must specify at least one parameter"); - } - - Some(PatchableFunctionEntry::from_prefix_and_entry(prefix.unwrap_or(0), entry.unwrap_or(0))) - }) -} - /// Spans that are collected when processing built-in attributes, /// that are useful for emitting diagnostics later. #[derive(Default)] @@ -353,6 +300,10 @@ fn process_builtin_attrs( AttributeKind::RustcOffloadKernel => { codegen_fn_attrs.flags |= CodegenFnAttrFlags::OFFLOAD_KERNEL } + AttributeKind::PatchableFunctionEntry { prefix, entry } => { + codegen_fn_attrs.patchable_function_entry = + Some(PatchableFunctionEntry::from_prefix_and_entry(*prefix, *entry)); + } _ => {} } } @@ -362,10 +313,6 @@ fn process_builtin_attrs( }; match name { - sym::patchable_function_entry => { - codegen_fn_attrs.patchable_function_entry = - parse_patchable_function_entry(tcx, attr); - } _ => {} } } diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 39727685aec1..6a97de4c2b13 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -136,39 +136,6 @@ pub(crate) struct RequiresRustAbi { pub span: Span, } -#[derive(Diagnostic)] -#[diag(codegen_ssa_expected_name_value_pair)] -pub(crate) struct ExpectedNameValuePair { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(codegen_ssa_unexpected_parameter_name)] -pub(crate) struct UnexpectedParameterName { - #[primary_span] - #[label] - pub span: Span, - pub prefix_nops: Symbol, - pub entry_nops: Symbol, -} - -#[derive(Diagnostic)] -#[diag(codegen_ssa_invalid_literal_value)] -pub(crate) struct InvalidLiteralValue { - #[primary_span] - #[label] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(codegen_ssa_out_of_range_integer)] -pub(crate) struct OutOfRangeInteger { - #[primary_span] - #[label] - pub span: Span, -} - #[derive(Diagnostic)] #[diag(codegen_ssa_copy_path_buf)] pub(crate) struct CopyPathBuf { diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 314f36d6132d..6e84d60f062f 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -879,6 +879,9 @@ pub enum AttributeKind { /// Represents `#[rustc_pass_by_value]` (used by the `rustc_pass_by_value` lint). PassByValue(Span), + /// Represents `#[patchable_function_entry]` + PatchableFunctionEntry { prefix: u8, entry: u8 }, + /// Represents `#[path]` Path(Symbol, Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index b55a5d0e29e1..1c13cef64fea 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -88,6 +88,7 @@ impl AttributeKind { Optimize(..) => No, ParenSugar(..) => No, PassByValue(..) => Yes, + PatchableFunctionEntry { .. } => Yes, Path(..) => No, PatternComplexityLimit { .. } => No, PinV2(..) => Yes, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 806f5c4d3ed9..c2ad644688fc 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -171,7 +171,7 @@ macro_rules! print_tup { print_tup!(A B C D E F G H); print_skip!(Span, (), ErrorGuaranteed); -print_disp!(u16, u128, usize, bool, NonZero, Limit); +print_disp!(u8, u16, u128, usize, bool, NonZero, Limit); print_debug!( Symbol, Ident, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 8074ae429892..3ddf51f6c9b1 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -324,6 +324,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcReallocator | AttributeKind::RustcNounwind | AttributeKind::RustcOffloadKernel + | AttributeKind::PatchableFunctionEntry { .. } ) => { /* do nothing */ } Attribute::Unparsed(attr_item) => { style = Some(attr_item.style); @@ -349,7 +350,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::deny | sym::forbid // need to be fixed - | sym::patchable_function_entry // FIXME(patchable_function_entry) | sym::deprecated_safe // FIXME(deprecated_safe) // internal | sym::prelude_import diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr index 3d51731df792..f817a0b0d91b 100644 --- a/tests/ui/attributes/malformed-attrs.stderr +++ b/tests/ui/attributes/malformed-attrs.stderr @@ -26,12 +26,6 @@ error[E0463]: can't find crate for `wloop` LL | extern crate wloop; | ^^^^^^^^^^^^^^^^^^^ can't find crate -error: malformed `patchable_function_entry` attribute input - --> $DIR/malformed-attrs.rs:114:1 - | -LL | #[patchable_function_entry] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` - error: malformed `allow` attribute input --> $DIR/malformed-attrs.rs:184:1 | @@ -444,6 +438,15 @@ LL | #[instruction_set] | = note: for more information, visit +error[E0539]: malformed `patchable_function_entry` attribute input + --> $DIR/malformed-attrs.rs:114:1 + | +LL | #[patchable_function_entry] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected this to be a list + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` + error[E0565]: malformed `coroutine` attribute input --> $DIR/malformed-attrs.rs:117:5 | diff --git a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs index 1e376c9ff3c1..5cbeccf1b0e4 100644 --- a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs +++ b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.rs @@ -1,17 +1,26 @@ #![feature(patchable_function_entry)] fn main() {} -#[patchable_function_entry(prefix_nops = 256, entry_nops = 0)]//~error: integer value out of range +#[patchable_function_entry(prefix_nops = 256, entry_nops = 0)] +//~^ ERROR malformed pub fn too_high_pnops() {} -#[patchable_function_entry(prefix_nops = "stringvalue", entry_nops = 0)]//~error: invalid literal value +#[patchable_function_entry(prefix_nops = "stringvalue", entry_nops = 0)] +//~^ ERROR malformed pub fn non_int_nop() {} -#[patchable_function_entry]//~error: malformed `patchable_function_entry` attribute input +#[patchable_function_entry] +//~^ ERROR malformed `patchable_function_entry` attribute input pub fn malformed_attribute() {} -#[patchable_function_entry(prefix_nops = 10, something = 0)]//~error: unexpected parameter name +#[patchable_function_entry(prefix_nops = 10, something = 0)] +//~^ ERROR malformed pub fn unexpected_parameter_name() {} -#[patchable_function_entry()]//~error: must specify at least one parameter +#[patchable_function_entry()] +//~^ ERROR malformed pub fn no_parameters_given() {} + +#[patchable_function_entry(prefix_nops = 255, prefix_nops = 255)] +//~^ ERROR malformed +pub fn duplicate_parameter() {} diff --git a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr index 9357a86c4153..43fc0c0518af 100644 --- a/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr +++ b/tests/ui/patchable-function-entry/patchable-function-entry-attribute.stderr @@ -1,32 +1,58 @@ -error: malformed `patchable_function_entry` attribute input - --> $DIR/patchable-function-entry-attribute.rs:10:1 - | -LL | #[patchable_function_entry] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` - -error: integer value out of range - --> $DIR/patchable-function-entry-attribute.rs:4:42 +error[E0539]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:4:1 | LL | #[patchable_function_entry(prefix_nops = 256, entry_nops = 0)] - | ^^^ value must be between `0` and `255` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---^^^^^^^^^^^^^^^^^^ + | | | + | | expected an integer literal in the range of 0..=255 + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` -error: invalid literal value - --> $DIR/patchable-function-entry-attribute.rs:7:42 +error[E0539]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:8:1 | LL | #[patchable_function_entry(prefix_nops = "stringvalue", entry_nops = 0)] - | ^^^^^^^^^^^^^ value must be an integer between `0` and `255` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------------^^^^^^^^^^^^^^^^^^ + | | | + | | expected an integer literal here + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` -error: unexpected parameter name - --> $DIR/patchable-function-entry-attribute.rs:13:46 +error[E0539]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:12:1 | -LL | #[patchable_function_entry(prefix_nops = 10, something = 0)] - | ^^^^^^^^^^^^^ expected `prefix_nops` or `entry_nops` +LL | #[patchable_function_entry] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected this to be a list + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` -error: must specify at least one parameter +error[E0539]: malformed `patchable_function_entry` attribute input --> $DIR/patchable-function-entry-attribute.rs:16:1 | +LL | #[patchable_function_entry(prefix_nops = 10, something = 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^ + | | | + | | valid arguments are `prefix_nops` or `entry_nops` + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` + +error[E0539]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:20:1 + | LL | #[patchable_function_entry()] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^--^ + | | | + | | expected this to be a list + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` -error: aborting due to 5 previous errors +error[E0538]: malformed `patchable_function_entry` attribute input + --> $DIR/patchable-function-entry-attribute.rs:24:1 + | +LL | #[patchable_function_entry(prefix_nops = 255, prefix_nops = 255)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^^^^^ + | | | + | | found `prefix_nops` used as a key more than once + | help: must be of the form: `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0538, E0539. +For more information about an error, try `rustc --explain E0538`. From a0b3ee2f76444c8cd82afc77246c868426290b37 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 19 Jan 2026 11:22:24 +0100 Subject: [PATCH 117/273] only process parsed attrs for codegen check --- .../rustc_codegen_ssa/src/codegen_attrs.rs | 447 +++++++++--------- 1 file changed, 216 insertions(+), 231 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 296c96ce4ae6..d55e134109d6 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -68,251 +68,236 @@ fn process_builtin_attrs( let mut interesting_spans = InterestingAttributeDiagnosticSpans::default(); let rust_target_features = tcx.rust_target_features(LOCAL_CRATE); - for attr in attrs.iter() { - if let hir::Attribute::Parsed(p) = attr { - match p { - AttributeKind::Cold(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD, - AttributeKind::ExportName { name, .. } => { - codegen_fn_attrs.symbol_name = Some(*name) + let parsed_attrs = attrs + .iter() + .filter_map(|attr| if let hir::Attribute::Parsed(attr) = attr { Some(attr) } else { None }); + for attr in parsed_attrs { + match attr { + AttributeKind::Cold(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD, + AttributeKind::ExportName { name, .. } => codegen_fn_attrs.symbol_name = Some(*name), + AttributeKind::Inline(inline, span) => { + codegen_fn_attrs.inline = *inline; + interesting_spans.inline = Some(*span); + } + AttributeKind::Naked(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED, + AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align), + AttributeKind::LinkName { name, .. } => { + // FIXME Remove check for foreign functions once #[link_name] on non-foreign + // functions is a hard error + if tcx.is_foreign_item(did) { + codegen_fn_attrs.symbol_name = Some(*name); } - AttributeKind::Inline(inline, span) => { - codegen_fn_attrs.inline = *inline; - interesting_spans.inline = Some(*span); + } + AttributeKind::LinkOrdinal { ordinal, span } => { + codegen_fn_attrs.link_ordinal = Some(*ordinal); + interesting_spans.link_ordinal = Some(*span); + } + AttributeKind::LinkSection { name, .. } => codegen_fn_attrs.link_section = Some(*name), + AttributeKind::NoMangle(attr_span) => { + interesting_spans.no_mangle = Some(*attr_span); + if tcx.opt_item_name(did.to_def_id()).is_some() { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE; + } else { + tcx.dcx() + .span_delayed_bug(*attr_span, "no_mangle should be on a named function"); } - AttributeKind::Naked(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED, - AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align), - AttributeKind::LinkName { name, .. } => { - // FIXME Remove check for foreign functions once #[link_name] on non-foreign - // functions is a hard error - if tcx.is_foreign_item(did) { - codegen_fn_attrs.symbol_name = Some(*name); - } - } - AttributeKind::LinkOrdinal { ordinal, span } => { - codegen_fn_attrs.link_ordinal = Some(*ordinal); - interesting_spans.link_ordinal = Some(*span); - } - AttributeKind::LinkSection { name, .. } => { - codegen_fn_attrs.link_section = Some(*name) - } - AttributeKind::NoMangle(attr_span) => { - interesting_spans.no_mangle = Some(*attr_span); - if tcx.opt_item_name(did.to_def_id()).is_some() { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE; + } + AttributeKind::Optimize(optimize, _) => codegen_fn_attrs.optimize = *optimize, + AttributeKind::TargetFeature { features, attr_span, was_forced } => { + let Some(sig) = tcx.hir_node_by_def_id(did).fn_sig() else { + tcx.dcx().span_delayed_bug(*attr_span, "target_feature applied to non-fn"); + continue; + }; + let safe_target_features = + matches!(sig.header.safety, hir::HeaderSafety::SafeTargetFeatures); + codegen_fn_attrs.safe_target_features = safe_target_features; + if safe_target_features && !was_forced { + if tcx.sess.target.is_like_wasm || tcx.sess.opts.actually_rustdoc { + // The `#[target_feature]` attribute is allowed on + // WebAssembly targets on all functions. Prior to stabilizing + // the `target_feature_11` feature, `#[target_feature]` was + // only permitted on unsafe functions because on most targets + // execution of instructions that are not supported is + // considered undefined behavior. For WebAssembly which is a + // 100% safe target at execution time it's not possible to + // execute undefined instructions, and even if a future + // feature was added in some form for this it would be a + // deterministic trap. There is no undefined behavior when + // executing WebAssembly so `#[target_feature]` is allowed + // on safe functions (but again, only for WebAssembly) + // + // Note that this is also allowed if `actually_rustdoc` so + // if a target is documenting some wasm-specific code then + // it's not spuriously denied. + // + // Now that `#[target_feature]` is permitted on safe functions, + // this exception must still exist for allowing the attribute on + // `main`, `start`, and other functions that are not usually + // allowed. } else { - tcx.dcx().span_delayed_bug( - *attr_span, - "no_mangle should be on a named function", - ); + check_target_feature_trait_unsafe(tcx, did, *attr_span); } } - AttributeKind::Optimize(optimize, _) => codegen_fn_attrs.optimize = *optimize, - AttributeKind::TargetFeature { features, attr_span, was_forced } => { - let Some(sig) = tcx.hir_node_by_def_id(did).fn_sig() else { - tcx.dcx().span_delayed_bug(*attr_span, "target_feature applied to non-fn"); - continue; + from_target_feature_attr( + tcx, + did, + features, + *was_forced, + rust_target_features, + &mut codegen_fn_attrs.target_features, + ); + } + AttributeKind::TrackCaller(attr_span) => { + let is_closure = tcx.is_closure_like(did.to_def_id()); + + if !is_closure + && let Some(fn_sig) = try_fn_sig(tcx, did, *attr_span) + && fn_sig.skip_binder().abi() != ExternAbi::Rust + { + tcx.dcx().emit_err(errors::RequiresRustAbi { span: *attr_span }); + } + if is_closure + && !tcx.features().closure_track_caller() + && !attr_span.allows_unstable(sym::closure_track_caller) + { + feature_err( + &tcx.sess, + sym::closure_track_caller, + *attr_span, + "`#[track_caller]` on closures is currently unstable", + ) + .emit(); + } + codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER + } + AttributeKind::Used { used_by, .. } => match used_by { + UsedBy::Compiler => codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED_COMPILER, + UsedBy::Linker => codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED_LINKER, + UsedBy::Default => { + let used_form = if tcx.sess.target.os == Os::Illumos { + // illumos' `ld` doesn't support a section header that would represent + // `#[used(linker)]`, see + // https://github.com/rust-lang/rust/issues/146169. For that target, + // downgrade as if `#[used(compiler)]` was requested and hope for the + // best. + CodegenFnAttrFlags::USED_COMPILER + } else { + CodegenFnAttrFlags::USED_LINKER }; - let safe_target_features = - matches!(sig.header.safety, hir::HeaderSafety::SafeTargetFeatures); - codegen_fn_attrs.safe_target_features = safe_target_features; - if safe_target_features && !was_forced { - if tcx.sess.target.is_like_wasm || tcx.sess.opts.actually_rustdoc { - // The `#[target_feature]` attribute is allowed on - // WebAssembly targets on all functions. Prior to stabilizing - // the `target_feature_11` feature, `#[target_feature]` was - // only permitted on unsafe functions because on most targets - // execution of instructions that are not supported is - // considered undefined behavior. For WebAssembly which is a - // 100% safe target at execution time it's not possible to - // execute undefined instructions, and even if a future - // feature was added in some form for this it would be a - // deterministic trap. There is no undefined behavior when - // executing WebAssembly so `#[target_feature]` is allowed - // on safe functions (but again, only for WebAssembly) - // - // Note that this is also allowed if `actually_rustdoc` so - // if a target is documenting some wasm-specific code then - // it's not spuriously denied. - // - // Now that `#[target_feature]` is permitted on safe functions, - // this exception must still exist for allowing the attribute on - // `main`, `start`, and other functions that are not usually - // allowed. - } else { - check_target_feature_trait_unsafe(tcx, did, *attr_span); + codegen_fn_attrs.flags |= used_form; + } + }, + AttributeKind::FfiConst(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_CONST, + AttributeKind::FfiPure(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_PURE, + AttributeKind::StdInternalSymbol(_) => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL + } + AttributeKind::Linkage(linkage, span) => { + let linkage = Some(*linkage); + + if tcx.is_foreign_item(did) { + codegen_fn_attrs.import_linkage = linkage; + + if tcx.is_mutable_static(did.into()) { + let mut diag = tcx.dcx().struct_span_err( + *span, + "extern mutable statics are not allowed with `#[linkage]`", + ); + diag.note( + "marking the extern static mutable would allow changing which \ + symbol the static references rather than make the target of the \ + symbol mutable", + ); + diag.emit(); + } + } else { + codegen_fn_attrs.linkage = linkage; + } + } + AttributeKind::Sanitize { span, .. } => { + interesting_spans.sanitize = Some(*span); + } + AttributeKind::ObjcClass { classname, .. } => { + codegen_fn_attrs.objc_class = Some(*classname); + } + AttributeKind::ObjcSelector { methname, .. } => { + codegen_fn_attrs.objc_selector = Some(*methname); + } + AttributeKind::EiiForeignItem => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM; + } + AttributeKind::EiiImpls(impls) => { + for i in impls { + let foreign_item = match i.resolution { + EiiImplResolution::Macro(def_id) => { + let Some(extern_item) = find_attr!( + tcx.get_all_attrs(def_id), + AttributeKind::EiiDeclaration(target) => target.foreign_item + ) else { + tcx.dcx().span_delayed_bug( + i.span, + "resolved to something that's not an EII", + ); + continue; + }; + extern_item } - } - from_target_feature_attr( - tcx, - did, - features, - *was_forced, - rust_target_features, - &mut codegen_fn_attrs.target_features, - ); - } - AttributeKind::TrackCaller(attr_span) => { - let is_closure = tcx.is_closure_like(did.to_def_id()); + EiiImplResolution::Known(decl) => decl.foreign_item, + EiiImplResolution::Error(_eg) => continue, + }; - if !is_closure - && let Some(fn_sig) = try_fn_sig(tcx, did, *attr_span) - && fn_sig.skip_binder().abi() != ExternAbi::Rust + // this is to prevent a bug where a single crate defines both the default and explicit implementation + // for an EII. In that case, both of them may be part of the same final object file. I'm not 100% sure + // what happens, either rustc deduplicates the symbol or llvm, or it's random/order-dependent. + // However, the fact that the default one of has weak linkage isn't considered and you sometimes get that + // the default implementation is used while an explicit implementation is given. + if + // if this is a default impl + i.is_default + // iterate over all implementations *in the current crate* + // (this is ok since we generate codegen fn attrs in the local crate) + // if any of them is *not default* then don't emit the alias. + && tcx.externally_implementable_items(LOCAL_CRATE).get(&foreign_item).expect("at least one").1.iter().any(|(_, imp)| !imp.is_default) { - tcx.dcx().emit_err(errors::RequiresRustAbi { span: *attr_span }); + continue; } - if is_closure - && !tcx.features().closure_track_caller() - && !attr_span.allows_unstable(sym::closure_track_caller) - { - feature_err( - &tcx.sess, - sym::closure_track_caller, - *attr_span, - "`#[track_caller]` on closures is currently unstable", - ) - .emit(); - } - codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER - } - AttributeKind::Used { used_by, .. } => match used_by { - UsedBy::Compiler => codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED_COMPILER, - UsedBy::Linker => codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED_LINKER, - UsedBy::Default => { - let used_form = if tcx.sess.target.os == Os::Illumos { - // illumos' `ld` doesn't support a section header that would represent - // `#[used(linker)]`, see - // https://github.com/rust-lang/rust/issues/146169. For that target, - // downgrade as if `#[used(compiler)]` was requested and hope for the - // best. - CodegenFnAttrFlags::USED_COMPILER - } else { - CodegenFnAttrFlags::USED_LINKER - }; - codegen_fn_attrs.flags |= used_form; - } - }, - AttributeKind::FfiConst(_) => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_CONST - } - AttributeKind::FfiPure(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_PURE, - AttributeKind::StdInternalSymbol(_) => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL - } - AttributeKind::Linkage(linkage, span) => { - let linkage = Some(*linkage); - if tcx.is_foreign_item(did) { - codegen_fn_attrs.import_linkage = linkage; - - if tcx.is_mutable_static(did.into()) { - let mut diag = tcx.dcx().struct_span_err( - *span, - "extern mutable statics are not allowed with `#[linkage]`", - ); - diag.note( - "marking the extern static mutable would allow changing which \ - symbol the static references rather than make the target of the \ - symbol mutable", - ); - diag.emit(); - } - } else { - codegen_fn_attrs.linkage = linkage; - } - } - AttributeKind::Sanitize { span, .. } => { - interesting_spans.sanitize = Some(*span); - } - AttributeKind::ObjcClass { classname, .. } => { - codegen_fn_attrs.objc_class = Some(*classname); - } - AttributeKind::ObjcSelector { methname, .. } => { - codegen_fn_attrs.objc_selector = Some(*methname); - } - AttributeKind::EiiForeignItem => { + codegen_fn_attrs.foreign_item_symbol_aliases.push(( + foreign_item, + if i.is_default { Linkage::LinkOnceAny } else { Linkage::External }, + Visibility::Default, + )); codegen_fn_attrs.flags |= CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM; } - AttributeKind::EiiImpls(impls) => { - for i in impls { - let foreign_item = match i.resolution { - EiiImplResolution::Macro(def_id) => { - let Some(extern_item) = find_attr!( - tcx.get_all_attrs(def_id), - AttributeKind::EiiDeclaration(target) => target.foreign_item - ) else { - tcx.dcx().span_delayed_bug( - i.span, - "resolved to something that's not an EII", - ); - continue; - }; - extern_item - } - EiiImplResolution::Known(decl) => decl.foreign_item, - EiiImplResolution::Error(_eg) => continue, - }; - - // this is to prevent a bug where a single crate defines both the default and explicit implementation - // for an EII. In that case, both of them may be part of the same final object file. I'm not 100% sure - // what happens, either rustc deduplicates the symbol or llvm, or it's random/order-dependent. - // However, the fact that the default one of has weak linkage isn't considered and you sometimes get that - // the default implementation is used while an explicit implementation is given. - if - // if this is a default impl - i.is_default - // iterate over all implementations *in the current crate* - // (this is ok since we generate codegen fn attrs in the local crate) - // if any of them is *not default* then don't emit the alias. - && tcx.externally_implementable_items(LOCAL_CRATE).get(&foreign_item).expect("at least one").1.iter().any(|(_, imp)| !imp.is_default) - { - continue; - } - - codegen_fn_attrs.foreign_item_symbol_aliases.push(( - foreign_item, - if i.is_default { Linkage::LinkOnceAny } else { Linkage::External }, - Visibility::Default, - )); - codegen_fn_attrs.flags |= CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM; - } - } - AttributeKind::ThreadLocal => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL - } - AttributeKind::InstructionSet(instruction_set) => { - codegen_fn_attrs.instruction_set = Some(*instruction_set) - } - AttributeKind::RustcAllocator => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR - } - AttributeKind::RustcDeallocator => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::DEALLOCATOR - } - AttributeKind::RustcReallocator => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::REALLOCATOR - } - AttributeKind::RustcAllocatorZeroed => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED - } - AttributeKind::RustcNounwind => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::NEVER_UNWIND - } - AttributeKind::RustcOffloadKernel => { - codegen_fn_attrs.flags |= CodegenFnAttrFlags::OFFLOAD_KERNEL - } - AttributeKind::PatchableFunctionEntry { prefix, entry } => { - codegen_fn_attrs.patchable_function_entry = - Some(PatchableFunctionEntry::from_prefix_and_entry(*prefix, *entry)); - } - _ => {} } - } - - let Some(name) = attr.name() else { - continue; - }; - - match name { + AttributeKind::ThreadLocal => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL + } + AttributeKind::InstructionSet(instruction_set) => { + codegen_fn_attrs.instruction_set = Some(*instruction_set) + } + AttributeKind::RustcAllocator => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR + } + AttributeKind::RustcDeallocator => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::DEALLOCATOR + } + AttributeKind::RustcReallocator => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::REALLOCATOR + } + AttributeKind::RustcAllocatorZeroed => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED + } + AttributeKind::RustcNounwind => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NEVER_UNWIND + } + AttributeKind::RustcOffloadKernel => { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::OFFLOAD_KERNEL + } + AttributeKind::PatchableFunctionEntry { prefix, entry } => { + codegen_fn_attrs.patchable_function_entry = + Some(PatchableFunctionEntry::from_prefix_and_entry(*prefix, *entry)); + } _ => {} } } From 7533a9aca918355a4ab4fe53cf0e7babcd5d30b8 Mon Sep 17 00:00:00 2001 From: Amerikrainian Date: Thu, 27 Nov 2025 19:03:41 -0600 Subject: [PATCH 118/273] Add manual checked ops lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/manual_checked_ops.rs | 170 +++++++++++++++++++++++++ tests/ui/manual_checked_ops.rs | 72 +++++++++++ tests/ui/manual_checked_ops.stderr | 50 ++++++++ 6 files changed, 296 insertions(+) create mode 100644 clippy_lints/src/manual_checked_ops.rs create mode 100644 tests/ui/manual_checked_ops.rs create mode 100644 tests/ui/manual_checked_ops.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index b5172267fcaa..6cdd834a4149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6608,6 +6608,7 @@ Released 2018-09-13 [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits [`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals +[`manual_checked_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_checked_ops [`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp [`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains [`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 549fc64d50e2..a04d133b0d72 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -297,6 +297,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::manual_assert::MANUAL_ASSERT_INFO, crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, + crate::manual_checked_ops::MANUAL_CHECKED_OPS_INFO, crate::manual_clamp::MANUAL_CLAMP_INFO, crate::manual_float_methods::MANUAL_IS_FINITE_INFO, crate::manual_float_methods::MANUAL_IS_INFINITE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6ea8a5b3d9d1..8eb3b580b669 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -201,6 +201,7 @@ mod manual_abs_diff; mod manual_assert; mod manual_async_fn; mod manual_bits; +mod manual_checked_ops; mod manual_clamp; mod manual_float_methods; mod manual_hash_one; @@ -863,6 +864,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)), Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))), Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))), + Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/manual_checked_ops.rs b/clippy_lints/src/manual_checked_ops.rs new file mode 100644 index 000000000000..6327567df336 --- /dev/null +++ b/clippy_lints/src/manual_checked_ops.rs @@ -0,0 +1,170 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{SpanlessEq, is_integer_literal}; +use rustc_hir::{AssignOpKind, BinOpKind, Block, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::declare_lint_pass; +use std::ops::ControlFlow; + +declare_clippy_lint! { + /// ### What it does + /// Detects manual zero checks before dividing integers, such as `if x != 0 { y / x }`. + /// + /// ### Why is this bad? + /// `checked_div` already handles the zero case and makes the intent clearer while avoiding a + /// panic from a manual division. + /// + /// ### Example + /// ```no_run + /// # let (a, b) = (10u32, 5u32); + /// if b != 0 { + /// let result = a / b; + /// println!("{result}"); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # let (a, b) = (10u32, 5u32); + /// if let Some(result) = a.checked_div(b) { + /// println!("{result}"); + /// } + /// ``` + #[clippy::version = "1.95.0"] + pub MANUAL_CHECKED_OPS, + complexity, + "manual zero checks before dividing integers" +} +declare_lint_pass!(ManualCheckedOps => [MANUAL_CHECKED_OPS]); + +#[derive(Copy, Clone)] +enum NonZeroBranch { + Then, + Else, +} + +impl LateLintPass<'_> for ManualCheckedOps { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::If(cond, then, r#else) = expr.kind + && !expr.span.from_expansion() + && let Some((divisor, branch)) = divisor_from_condition(cond) + // This lint is intended for unsigned integers only. + // + // For signed integers, the most direct refactor to `checked_div` is often not + // semantically equivalent to the original guard. For example, `rhs > 0` deliberately + // excludes negative divisors, while `checked_div` would return `Some` for `rhs = -2`. + // Also, `checked_div` can return `None` for `MIN / -1`, which requires additional + // handling beyond the zero check. + && is_unsigned_integer(cx, divisor) + && let Some(block) = branch_block(then, r#else, branch) + { + let mut eq = SpanlessEq::new(cx).deny_side_effects().paths_by_resolution(); + if !eq.eq_expr(divisor, divisor) { + return; + } + + let mut division_spans = Vec::new(); + let mut first_use = None; + + let found_early_use = for_each_expr_without_closures(block, |e| { + if let ExprKind::Binary(binop, lhs, rhs) = e.kind + && binop.node == BinOpKind::Div + && eq.eq_expr(rhs, divisor) + && is_unsigned_integer(cx, lhs) + { + match first_use { + None => first_use = Some(UseKind::Division), + Some(UseKind::Other) => return ControlFlow::Break(()), + Some(UseKind::Division) => {}, + } + + division_spans.push(e.span); + + ControlFlow::<(), _>::Continue(Descend::No) + } else if let ExprKind::AssignOp(op, lhs, rhs) = e.kind + && op.node == AssignOpKind::DivAssign + && eq.eq_expr(rhs, divisor) + && is_unsigned_integer(cx, lhs) + { + match first_use { + None => first_use = Some(UseKind::Division), + Some(UseKind::Other) => return ControlFlow::Break(()), + Some(UseKind::Division) => {}, + } + + division_spans.push(e.span); + + ControlFlow::<(), _>::Continue(Descend::No) + } else if eq.eq_expr(e, divisor) { + if first_use.is_none() { + first_use = Some(UseKind::Other); + return ControlFlow::Break(()); + } + ControlFlow::<(), _>::Continue(Descend::Yes) + } else { + ControlFlow::<(), _>::Continue(Descend::Yes) + } + }); + + if found_early_use.is_some() || first_use != Some(UseKind::Division) || division_spans.is_empty() { + return; + } + + span_lint_and_then(cx, MANUAL_CHECKED_OPS, cond.span, "manual checked division", |diag| { + diag.span_label(cond.span, "check performed here"); + if let Some((first, rest)) = division_spans.split_first() { + diag.span_label(*first, "division performed here"); + if !rest.is_empty() { + diag.span_labels(rest.to_vec(), "... and here"); + } + } + diag.help("consider using `checked_div`"); + }); + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +enum UseKind { + Division, + Other, +} + +fn divisor_from_condition<'tcx>(cond: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, NonZeroBranch)> { + let ExprKind::Binary(binop, lhs, rhs) = cond.kind else { + return None; + }; + + match binop.node { + BinOpKind::Ne | BinOpKind::Lt if is_zero(lhs) => Some((rhs, NonZeroBranch::Then)), + BinOpKind::Ne | BinOpKind::Gt if is_zero(rhs) => Some((lhs, NonZeroBranch::Then)), + BinOpKind::Eq if is_zero(lhs) => Some((rhs, NonZeroBranch::Else)), + BinOpKind::Eq if is_zero(rhs) => Some((lhs, NonZeroBranch::Else)), + _ => None, + } +} + +fn branch_block<'tcx>( + then: &'tcx Expr<'tcx>, + r#else: Option<&'tcx Expr<'tcx>>, + branch: NonZeroBranch, +) -> Option<&'tcx Block<'tcx>> { + match branch { + NonZeroBranch::Then => match then.kind { + ExprKind::Block(block, _) => Some(block), + _ => None, + }, + NonZeroBranch::Else => match r#else?.kind { + ExprKind::Block(block, _) => Some(block), + _ => None, + }, + } +} + +fn is_zero(expr: &Expr<'_>) -> bool { + is_integer_literal(expr, 0) +} + +fn is_unsigned_integer(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Uint(_)) +} diff --git a/tests/ui/manual_checked_ops.rs b/tests/ui/manual_checked_ops.rs new file mode 100644 index 000000000000..93630faa1086 --- /dev/null +++ b/tests/ui/manual_checked_ops.rs @@ -0,0 +1,72 @@ +#![warn(clippy::manual_checked_ops)] + +fn main() { + let mut a = 10u32; + let mut b = 5u32; + + // Should trigger lint + if b != 0 { + //~^ manual_checked_ops + let _result = a / b; + let _another = (a + 1) / b; + } + + // Should trigger lint (compound assignment) + if b != 0 { + //~^ manual_checked_ops + a /= b; + } + + if b > 0 { + //~^ manual_checked_ops + let _result = a / b; + } + + if b == 0 { + //~^ manual_checked_ops + println!("zero"); + } else { + let _result = a / b; + } + + // Should NOT trigger (already using checked_div) + if let Some(result) = b.checked_div(a) { + println!("{result}"); + } + + // Should NOT trigger (signed integers are not linted) + let c = -5i32; + if c != 0 { + let _result = 10 / c; + } + + // Should NOT trigger (side effects in divisor) + if counter() > 0 { + let _ = 32 / counter(); + } + + // Should NOT trigger (divisor used before division) + if b > 0 { + use_value(b); + let _ = a / b; + } + + // Should NOT trigger (divisor may change during evaluation) + if b > 0 { + g(inc_and_return_value(&mut b), a / b); + } +} + +fn counter() -> u32 { + println!("counter"); + 1 +} + +fn use_value(_v: u32) {} + +fn inc_and_return_value(x: &mut u32) -> u32 { + *x += 1; + *x +} + +fn g(_lhs: u32, _rhs: u32) {} diff --git a/tests/ui/manual_checked_ops.stderr b/tests/ui/manual_checked_ops.stderr new file mode 100644 index 000000000000..311f319bad14 --- /dev/null +++ b/tests/ui/manual_checked_ops.stderr @@ -0,0 +1,50 @@ +error: manual checked division + --> tests/ui/manual_checked_ops.rs:8:8 + | +LL | if b != 0 { + | ^^^^^^ check performed here +LL | +LL | let _result = a / b; + | ----- division performed here +LL | let _another = (a + 1) / b; + | ----------- ... and here + | + = help: consider using `checked_div` + = note: `-D clippy::manual-checked-ops` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_checked_ops)]` + +error: manual checked division + --> tests/ui/manual_checked_ops.rs:15:8 + | +LL | if b != 0 { + | ^^^^^^ check performed here +LL | +LL | a /= b; + | ------ division performed here + | + = help: consider using `checked_div` + +error: manual checked division + --> tests/ui/manual_checked_ops.rs:20:8 + | +LL | if b > 0 { + | ^^^^^ check performed here +LL | +LL | let _result = a / b; + | ----- division performed here + | + = help: consider using `checked_div` + +error: manual checked division + --> tests/ui/manual_checked_ops.rs:25:8 + | +LL | if b == 0 { + | ^^^^^^ check performed here +... +LL | let _result = a / b; + | ----- division performed here + | + = help: consider using `checked_div` + +error: aborting due to 4 previous errors + From 93929ef064b1497182acd2929137d6e4886c0e47 Mon Sep 17 00:00:00 2001 From: joboet Date: Tue, 20 Jan 2026 11:15:12 +0100 Subject: [PATCH 119/273] std: use 64-bit `clock_nanosleep` on Linux if available --- library/std/src/sys/thread/unix.rs | 50 +++++++++++++++++++++++ src/tools/miri/src/shims/extern_static.rs | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index b1f27c32fedd..cf3d31835b09 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -609,6 +609,56 @@ pub fn sleep(dur: Duration) { pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; + #[cfg(all( + target_os = "linux", + target_env = "gnu", + target_pointer_width = "32", + not(target_arch = "riscv32") + ))] + { + use crate::sys::pal::time::__timespec64; + use crate::sys::pal::weak::weak; + + // This got added in glibc 2.31, along with a 64-bit `clock_gettime` + // function. + weak! { + fn __clock_nanosleep_time64( + clock_id: libc::clockid_t, + flags: libc::c_int, + req: *const __timespec64, + rem: *mut __timespec64, + ) -> libc::c_int; + } + + if let Some(clock_nanosleep) = __clock_nanosleep_time64.get() { + let ts = deadline.into_inner().into_timespec().to_timespec64(); + loop { + let r = unsafe { + clock_nanosleep( + crate::sys::time::Instant::CLOCK_ID, + libc::TIMER_ABSTIME, + &ts, + core::ptr::null_mut(), + ) + }; + + match r { + 0 => return, + libc::EINTR => continue, + // If the underlying kernel doesn't support the 64-bit + // syscall, `__clock_nanosleep_time64` will fail. The + // error code nowadays is EOVERFLOW, but it used to be + // ENOSYS – so just don't rely on any particular value. + // The parameters are all valid, so the only reasons + // why the call might fail are EINTR and the call not + // being supported. Fall through to the clamping version + // in that case. + _ => break, + } + } + } + } + let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else { // The deadline is further in the future then can be passed to // clock_nanosleep. We have to use Self::sleep instead. This might diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs index fc9971641088..6c7f3470600b 100644 --- a/src/tools/miri/src/shims/extern_static.rs +++ b/src/tools/miri/src/shims/extern_static.rs @@ -55,7 +55,7 @@ impl<'tcx> MiriMachine<'tcx> { Os::Linux => { Self::null_ptr_extern_statics( ecx, - &["__cxa_thread_atexit_impl", "__clock_gettime64"], + &["__cxa_thread_atexit_impl", "__clock_gettime64", "__clock_nanosleep_time64"], )?; Self::weak_symbol_extern_statics(ecx, &["getrandom", "gettid", "statx"])?; } From d977471ce22c379cd7f8a53d907aecab67d05195 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Tue, 20 Jan 2026 17:09:52 +0800 Subject: [PATCH 120/273] LoongArch: Fix call-llvm-intrinsics test --- tests/codegen-llvm/loongarch-abi/call-llvm-intrinsics.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/codegen-llvm/loongarch-abi/call-llvm-intrinsics.rs b/tests/codegen-llvm/loongarch-abi/call-llvm-intrinsics.rs index 9a50f7b8e3a5..36eb2dde7afb 100644 --- a/tests/codegen-llvm/loongarch-abi/call-llvm-intrinsics.rs +++ b/tests/codegen-llvm/loongarch-abi/call-llvm-intrinsics.rs @@ -23,9 +23,7 @@ pub fn do_call() { unsafe { // Ensure that we `call` LLVM intrinsics instead of trying to `invoke` them - // CHECK: store float 4.000000e+00, ptr %{{.}}, align 4 - // CHECK: load float, ptr %{{.}}, align 4 - // CHECK: call float @llvm.sqrt.f32(float %{{.}} + // CHECK: call float @llvm.sqrt.f32(float 4.000000e+00) sqrt(4.0); } } From bc0cce1595ee614fd91898657b4402570880cc48 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Sun, 9 Nov 2025 20:48:55 +0300 Subject: [PATCH 121/273] ptr_aligment_type: add more APIs --- library/alloc/src/rc.rs | 12 +- library/alloc/src/sync.rs | 12 +- library/core/src/alloc/layout.rs | 136 +++++++++++++----- library/core/src/mem/mod.rs | 5 + library/core/src/ptr/alignment.rs | 74 ++++++++++ ...ace.PreCodegen.after.32bit.panic-abort.mir | 56 +++++--- ...ce.PreCodegen.after.32bit.panic-unwind.mir | 56 +++++--- ...ace.PreCodegen.after.64bit.panic-abort.mir | 56 +++++--- ...ce.PreCodegen.after.64bit.panic-unwind.mir | 56 +++++--- tests/mir-opt/pre-codegen/drop_boxed_slice.rs | 2 +- tests/ui/thir-print/offset_of.stdout | 18 +-- 11 files changed, 330 insertions(+), 153 deletions(-) diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index f58ebd488d7c..cec41524325e 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -252,7 +252,7 @@ use core::intrinsics::abort; #[cfg(not(no_global_oom_handling))] use core::iter; use core::marker::{PhantomData, Unsize}; -use core::mem::{self, ManuallyDrop, align_of_val_raw}; +use core::mem::{self, ManuallyDrop}; use core::num::NonZeroUsize; use core::ops::{CoerceUnsized, Deref, DerefMut, DerefPure, DispatchFromDyn, LegacyReceiver}; #[cfg(not(no_global_oom_handling))] @@ -3845,15 +3845,15 @@ unsafe fn data_offset(ptr: *const T) -> usize { // Because RcInner is repr(C), it will always be the last field in memory. // SAFETY: since the only unsized types possible are slices, trait objects, // and extern types, the input safety requirement is currently enough to - // satisfy the requirements of align_of_val_raw; this is an implementation + // satisfy the requirements of Alignment::of_val_raw; this is an implementation // detail of the language that must not be relied upon outside of std. - unsafe { data_offset_align(Alignment::new_unchecked(align_of_val_raw(ptr))) } + unsafe { data_offset_alignment(Alignment::of_val_raw(ptr)) } } #[inline] -fn data_offset_align(align: Alignment) -> usize { +fn data_offset_alignment(alignment: Alignment) -> usize { let layout = Layout::new::>(); - layout.size() + layout.padding_needed_for(align) + layout.size() + layout.padding_needed_for(alignment) } /// A uniquely owned [`Rc`]. @@ -4478,7 +4478,7 @@ impl UniqueRcUninit { /// Returns the pointer to be written into to initialize the [`Rc`]. fn data_ptr(&mut self) -> *mut T { - let offset = data_offset_align(self.layout_for_value.alignment()); + let offset = data_offset_alignment(self.layout_for_value.alignment()); unsafe { self.ptr.as_ptr().byte_add(offset) as *mut T } } diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index fc44a468c8a4..a5e4fab916ab 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -19,7 +19,7 @@ use core::intrinsics::abort; #[cfg(not(no_global_oom_handling))] use core::iter; use core::marker::{PhantomData, Unsize}; -use core::mem::{self, ManuallyDrop, align_of_val_raw}; +use core::mem::{self, ManuallyDrop}; use core::num::NonZeroUsize; use core::ops::{CoerceUnsized, Deref, DerefMut, DerefPure, DispatchFromDyn, LegacyReceiver}; #[cfg(not(no_global_oom_handling))] @@ -4206,15 +4206,15 @@ unsafe fn data_offset(ptr: *const T) -> usize { // Because ArcInner is repr(C), it will always be the last field in memory. // SAFETY: since the only unsized types possible are slices, trait objects, // and extern types, the input safety requirement is currently enough to - // satisfy the requirements of align_of_val_raw; this is an implementation + // satisfy the requirements of Alignment::of_val_raw; this is an implementation // detail of the language that must not be relied upon outside of std. - unsafe { data_offset_align(Alignment::new_unchecked(align_of_val_raw(ptr))) } + unsafe { data_offset_alignment(Alignment::of_val_raw(ptr)) } } #[inline] -fn data_offset_align(align: Alignment) -> usize { +fn data_offset_alignment(alignment: Alignment) -> usize { let layout = Layout::new::>(); - layout.size() + layout.padding_needed_for(align) + layout.size() + layout.padding_needed_for(alignment) } /// A unique owning pointer to an [`ArcInner`] **that does not imply the contents are initialized,** @@ -4258,7 +4258,7 @@ impl UniqueArcUninit { /// Returns the pointer to be written into to initialize the [`Arc`]. fn data_ptr(&mut self) -> *mut T { - let offset = data_offset_align(self.layout_for_value.alignment()); + let offset = data_offset_alignment(self.layout_for_value.alignment()); unsafe { self.ptr.as_ptr().byte_add(offset) as *mut T } } diff --git a/library/core/src/alloc/layout.rs b/library/core/src/alloc/layout.rs index 3a2111350a4e..4bffdd17696f 100644 --- a/library/core/src/alloc/layout.rs +++ b/library/core/src/alloc/layout.rs @@ -67,15 +67,16 @@ impl Layout { #[inline] const fn is_size_align_valid(size: usize, align: usize) -> bool { - let Some(align) = Alignment::new(align) else { return false }; - if size > Self::max_size_for_align(align) { - return false; - } - true + let Some(alignment) = Alignment::new(align) else { return false }; + Self::is_size_alignment_valid(size, alignment) + } + + const fn is_size_alignment_valid(size: usize, alignment: Alignment) -> bool { + size <= Self::max_size_for_alignment(alignment) } #[inline(always)] - const fn max_size_for_align(align: Alignment) -> usize { + const fn max_size_for_alignment(alignment: Alignment) -> usize { // (power-of-two implies align != 0.) // Rounded up size is: @@ -93,18 +94,28 @@ impl Layout { // SAFETY: the maximum possible alignment is `isize::MAX + 1`, // so the subtraction cannot overflow. - unsafe { unchecked_sub(isize::MAX as usize + 1, align.as_usize()) } + unsafe { unchecked_sub(isize::MAX as usize + 1, alignment.as_usize()) } } - /// Internal helper constructor to skip revalidating alignment validity. + /// Constructs a `Layout` from a given `size` and `alignment`, + /// or returns `LayoutError` if any of the following conditions + /// are not met: + /// + /// * `size`, when rounded up to the nearest multiple of `alignment`, + /// must not overflow `isize` (i.e., the rounded value must be + /// less than or equal to `isize::MAX`). + #[unstable(feature = "ptr_alignment_type", issue = "102070")] #[inline] - const fn from_size_alignment(size: usize, align: Alignment) -> Result { - if size > Self::max_size_for_align(align) { - return Err(LayoutError); + pub const fn from_size_alignment( + size: usize, + alignment: Alignment, + ) -> Result { + if Layout::is_size_alignment_valid(size, alignment) { + // SAFETY: Layout::size invariants checked above. + Ok(Layout { size, align: alignment }) + } else { + Err(LayoutError) } - - // SAFETY: Layout::size invariants checked above. - Ok(Layout { size, align }) } /// Creates a layout, bypassing all checks. @@ -132,6 +143,30 @@ impl Layout { unsafe { Layout { size, align: mem::transmute(align) } } } + /// Creates a layout, bypassing all checks. + /// + /// # Safety + /// + /// This function is unsafe as it does not verify the preconditions from + /// [`Layout::from_size_alignment`]. + #[unstable(feature = "ptr_alignment_type", issue = "102070")] + #[must_use] + #[inline] + #[track_caller] + pub const unsafe fn from_size_alignment_unchecked(size: usize, alignment: Alignment) -> Self { + assert_unsafe_precondition!( + check_library_ub, + "Layout::from_size_alignment_unchecked requires \ + that the rounded-up allocation size does not exceed isize::MAX", + ( + size: usize = size, + alignment: Alignment = alignment, + ) => Layout::is_size_alignment_valid(size, alignment) + ); + // SAFETY: the caller is required to uphold the preconditions. + Layout { size, align: alignment } + } + /// The minimum size in bytes for a memory block of this layout. #[stable(feature = "alloc_layout", since = "1.28.0")] #[rustc_const_stable(feature = "const_alloc_layout_size_align", since = "1.50.0")] @@ -153,6 +188,16 @@ impl Layout { self.align.as_usize() } + /// The minimum byte alignment for a memory block of this layout. + /// + /// The returned alignment is guaranteed to be a power of two. + #[unstable(feature = "ptr_alignment_type", issue = "102070")] + #[must_use = "this returns the minimum alignment, without modifying the layout"] + #[inline] + pub const fn alignment(&self) -> Alignment { + self.align + } + /// Constructs a `Layout` suitable for holding a value of type `T`. #[stable(feature = "alloc_layout", since = "1.28.0")] #[rustc_const_stable(feature = "alloc_layout_const_new", since = "1.42.0")] @@ -170,9 +215,9 @@ impl Layout { #[must_use] #[inline] pub const fn for_value(t: &T) -> Self { - let (size, align) = (size_of_val(t), align_of_val(t)); + let (size, alignment) = (size_of_val(t), Alignment::of_val(t)); // SAFETY: see rationale in `new` for why this is using the unsafe variant - unsafe { Layout::from_size_align_unchecked(size, align) } + unsafe { Layout::from_size_alignment_unchecked(size, alignment) } } /// Produces layout describing a record that could be used to @@ -204,11 +249,12 @@ impl Layout { /// [extern type]: ../../unstable-book/language-features/extern-types.html #[unstable(feature = "layout_for_ptr", issue = "69835")] #[must_use] + #[inline] pub const unsafe fn for_value_raw(t: *const T) -> Self { // SAFETY: we pass along the prerequisites of these functions to the caller - let (size, align) = unsafe { (mem::size_of_val_raw(t), mem::align_of_val_raw(t)) }; + let (size, alignment) = unsafe { (mem::size_of_val_raw(t), Alignment::of_val_raw(t)) }; // SAFETY: see rationale in `new` for why this is using the unsafe variant - unsafe { Layout::from_size_align_unchecked(size, align) } + unsafe { Layout::from_size_alignment_unchecked(size, alignment) } } /// Creates a `NonNull` that is dangling, but well-aligned for this Layout. @@ -243,13 +289,33 @@ impl Layout { #[rustc_const_stable(feature = "const_alloc_layout", since = "1.85.0")] #[inline] pub const fn align_to(&self, align: usize) -> Result { - if let Some(align) = Alignment::new(align) { - Layout::from_size_alignment(self.size, Alignment::max(self.align, align)) + if let Some(alignment) = Alignment::new(align) { + self.adjust_alignment_to(alignment) } else { Err(LayoutError) } } + /// Creates a layout describing the record that can hold a value + /// of the same layout as `self`, but that also is aligned to + /// alignment `alignment`. + /// + /// If `self` already meets the prescribed alignment, then returns + /// `self`. + /// + /// Note that this method does not add any padding to the overall + /// size, regardless of whether the returned layout has a different + /// alignment. In other words, if `K` has size 16, `K.align_to(32)` + /// will *still* have size 16. + /// + /// Returns an error if the combination of `self.size()` and the given + /// `alignment` violates the conditions listed in [`Layout::from_size_alignment`]. + #[unstable(feature = "ptr_alignment_type", issue = "102070")] + #[inline] + pub const fn adjust_alignment_to(&self, alignment: Alignment) -> Result { + Layout::from_size_alignment(self.size, Alignment::max(self.align, alignment)) + } + /// Returns the amount of padding we must insert after `self` /// to ensure that the following address will satisfy `alignment`. /// @@ -267,7 +333,7 @@ impl Layout { #[must_use = "this returns the padding needed, without modifying the `Layout`"] #[inline] pub const fn padding_needed_for(&self, alignment: Alignment) -> usize { - let len_rounded_up = self.size_rounded_up_to_custom_align(alignment); + let len_rounded_up = self.size_rounded_up_to_custom_alignment(alignment); // SAFETY: Cannot overflow because the rounded-up value is never less unsafe { unchecked_sub(len_rounded_up, self.size) } } @@ -277,7 +343,7 @@ impl Layout { /// This can return at most `Alignment::MAX` (aka `isize::MAX + 1`) /// because the original size is at most `isize::MAX`. #[inline] - const fn size_rounded_up_to_custom_align(&self, align: Alignment) -> usize { + const fn size_rounded_up_to_custom_alignment(&self, alignment: Alignment) -> usize { // SAFETY: // Rounded up value is: // size_rounded_up = (size + align - 1) & !(align - 1); @@ -297,7 +363,7 @@ impl Layout { // (Size 0 Align MAX is already aligned, so stays the same, but things like // Size 1 Align MAX or Size isize::MAX Align 2 round up to `isize::MAX + 1`.) unsafe { - let align_m1 = unchecked_sub(align.as_usize(), 1); + let align_m1 = unchecked_sub(alignment.as_usize(), 1); unchecked_add(self.size, align_m1) & !align_m1 } } @@ -317,10 +383,10 @@ impl Layout { // > `size`, when rounded up to the nearest multiple of `align`, // > must not overflow isize (i.e., the rounded value must be // > less than or equal to `isize::MAX`) - let new_size = self.size_rounded_up_to_custom_align(self.align); + let new_size = self.size_rounded_up_to_custom_alignment(self.align); // SAFETY: padded size is guaranteed to not exceed `isize::MAX`. - unsafe { Layout::from_size_align_unchecked(new_size, self.align()) } + unsafe { Layout::from_size_alignment_unchecked(new_size, self.alignment()) } } /// Creates a layout describing the record for `n` instances of @@ -426,8 +492,8 @@ impl Layout { #[rustc_const_stable(feature = "const_alloc_layout", since = "1.85.0")] #[inline] pub const fn extend(&self, next: Self) -> Result<(Self, usize), LayoutError> { - let new_align = Alignment::max(self.align, next.align); - let offset = self.size_rounded_up_to_custom_align(next.align); + let new_alignment = Alignment::max(self.align, next.align); + let offset = self.size_rounded_up_to_custom_alignment(next.align); // SAFETY: `offset` is at most `isize::MAX + 1` (such as from aligning // to `Alignment::MAX`) and `next.size` is at most `isize::MAX` (from the @@ -435,7 +501,7 @@ impl Layout { // `isize::MAX + 1 + isize::MAX`, which is `usize::MAX`, and cannot overflow. let new_size = unsafe { unchecked_add(offset, next.size) }; - if let Ok(layout) = Layout::from_size_alignment(new_size, new_align) { + if let Ok(layout) = Layout::from_size_alignment(new_size, new_alignment) { Ok((layout, offset)) } else { Err(LayoutError) @@ -496,7 +562,7 @@ impl Layout { #[inline] const fn inner(element_layout: Layout, n: usize) -> Result { - let Layout { size: element_size, align } = element_layout; + let Layout { size: element_size, align: alignment } = element_layout; // We need to check two things about the size: // - That the total size won't overflow a `usize`, and @@ -504,7 +570,7 @@ impl Layout { // By using division we can check them both with a single threshold. // That'd usually be a bad idea, but thankfully here the element size // and alignment are constants, so the compiler will fold all of it. - if element_size != 0 && n > Layout::max_size_for_align(align) / element_size { + if element_size != 0 && n > Layout::max_size_for_alignment(alignment) / element_size { return Err(LayoutError); } @@ -517,17 +583,9 @@ impl Layout { // SAFETY: We just checked above that the `array_size` will not // exceed `isize::MAX` even when rounded up to the alignment. // And `Alignment` guarantees it's a power of two. - unsafe { Ok(Layout::from_size_align_unchecked(array_size, align.as_usize())) } + unsafe { Ok(Layout::from_size_alignment_unchecked(array_size, alignment)) } } } - - /// Perma-unstable access to `align` as `Alignment` type. - #[unstable(issue = "none", feature = "std_internals")] - #[doc(hidden)] - #[inline] - pub const fn alignment(&self) -> Alignment { - self.align - } } #[stable(feature = "alloc_layout", since = "1.28.0")] diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 1671c8219de1..7c486875a826 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -9,6 +9,7 @@ use crate::alloc::Layout; use crate::clone::TrivialClone; use crate::marker::{Destruct, DiscriminantKind}; use crate::panic::const_assert; +use crate::ptr::Alignment; use crate::{clone, cmp, fmt, hash, intrinsics, ptr}; mod manually_drop; @@ -1257,6 +1258,10 @@ pub trait SizedTypeProperties: Sized { #[lang = "mem_align_const"] const ALIGN: usize = intrinsics::align_of::(); + #[doc(hidden)] + #[unstable(feature = "ptr_alignment_type", issue = "102070")] + const ALIGNMENT: Alignment = Alignment::of::(); + /// `true` if this type requires no storage. /// `false` if its [size](size_of) is greater than zero. /// diff --git a/library/core/src/ptr/alignment.rs b/library/core/src/ptr/alignment.rs index bc7d3a1de715..42c95e6c9cd2 100644 --- a/library/core/src/ptr/alignment.rs +++ b/library/core/src/ptr/alignment.rs @@ -1,5 +1,6 @@ #![allow(clippy::enum_clike_unportable_variant)] +use crate::marker::MetaSized; use crate::num::NonZero; use crate::ub_checks::assert_unsafe_precondition; use crate::{cmp, fmt, hash, mem, num}; @@ -50,6 +51,79 @@ impl Alignment { const { Alignment::new(align_of::()).unwrap() } } + /// Returns the [ABI]-required minimum alignment of the type of the value that `val` points to. + /// + /// Every reference to a value of the type `T` must be a multiple of this number. + /// + /// [ABI]: https://en.wikipedia.org/wiki/Application_binary_interface + /// + /// # Examples + /// + /// ``` + /// #![feature(ptr_alignment_type)] + /// use std::ptr::Alignment; + /// + /// assert_eq!(Alignment::of_val(&5i32).as_usize(), 4); + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "ptr_alignment_type", issue = "102070")] + pub const fn of_val(val: &T) -> Self { + let align = mem::align_of_val(val); + // SAFETY: `align_of_val` returns valid alignment + unsafe { Alignment::new_unchecked(align) } + } + + /// Returns the [ABI]-required minimum alignment of the type of the value that `val` points to. + /// + /// Every reference to a value of the type `T` must be a multiple of this number. + /// + /// [ABI]: https://en.wikipedia.org/wiki/Application_binary_interface + /// + /// # Safety + /// + /// This function is only safe to call if the following conditions hold: + /// + /// - If `T` is `Sized`, this function is always safe to call. + /// - If the unsized tail of `T` is: + /// - a [slice], then the length of the slice tail must be an initialized + /// integer, and the size of the *entire value* + /// (dynamic tail length + statically sized prefix) must fit in `isize`. + /// For the special case where the dynamic tail length is 0, this function + /// is safe to call. + /// - a [trait object], then the vtable part of the pointer must point + /// to a valid vtable acquired by an unsizing coercion, and the size + /// of the *entire value* (dynamic tail length + statically sized prefix) + /// must fit in `isize`. + /// - an (unstable) [extern type], then this function is always safe to + /// call, but may panic or otherwise return the wrong value, as the + /// extern type's layout is not known. This is the same behavior as + /// [`Alignment::of_val`] on a reference to a type with an extern type tail. + /// - otherwise, it is conservatively not allowed to call this function. + /// + /// [trait object]: ../../book/ch17-02-trait-objects.html + /// [extern type]: ../../unstable-book/language-features/extern-types.html + /// + /// # Examples + /// + /// ``` + /// #![feature(ptr_alignment_type)] + /// #![feature(layout_for_ptr)] + /// use std::ptr::Alignment; + /// + /// assert_eq!(unsafe { Alignment::of_val_raw(&5i32) }.as_usize(), 4); + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "ptr_alignment_type", issue = "102070")] + // #[unstable(feature = "layout_for_ptr", issue = "69835")] + pub const unsafe fn of_val_raw(val: *const T) -> Self { + // SAFETY: precondition propagated to the caller + let align = unsafe { mem::align_of_val_raw(val) }; + // SAFETY: `align_of_val_raw` returns valid alignment + unsafe { Alignment::new_unchecked(align) } + } + /// Creates an `Alignment` from a `usize`, or returns `None` if it's /// not a power of two. /// diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir index 9202814adec7..6ffadd4c51db 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-abort.mir @@ -12,32 +12,32 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 3 { let _8: std::ptr::alignment::AlignmentEnum; scope 4 { - scope 12 (inlined Layout::size) { + scope 17 (inlined Layout::size) { } - scope 13 (inlined std::ptr::Unique::<[T]>::cast::) { - scope 14 (inlined NonNull::<[T]>::cast::) { - scope 15 (inlined NonNull::<[T]>::as_ptr) { + scope 18 (inlined std::ptr::Unique::<[T]>::cast::) { + scope 19 (inlined NonNull::<[T]>::cast::) { + scope 20 (inlined NonNull::<[T]>::as_ptr) { } } } - scope 16 (inlined as From>>::from) { - scope 17 (inlined std::ptr::Unique::::as_non_null_ptr) { + scope 21 (inlined as From>>::from) { + scope 22 (inlined std::ptr::Unique::::as_non_null_ptr) { } } - scope 18 (inlined ::deallocate) { - scope 19 (inlined std::alloc::Global::deallocate_impl) { - scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { + scope 23 (inlined ::deallocate) { + scope 24 (inlined std::alloc::Global::deallocate_impl) { + scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { let mut _9: *mut u8; - scope 21 (inlined Layout::size) { + scope 26 (inlined Layout::size) { } - scope 22 (inlined NonNull::::as_ptr) { + scope 27 (inlined NonNull::::as_ptr) { } - scope 23 (inlined std::alloc::dealloc) { + scope 28 (inlined std::alloc::dealloc) { let mut _10: usize; - scope 24 (inlined Layout::size) { + scope 29 (inlined Layout::size) { } - scope 25 (inlined Layout::align) { - scope 26 (inlined std::ptr::Alignment::as_usize) { + scope 30 (inlined Layout::align) { + scope 31 (inlined std::ptr::Alignment::as_usize) { } } } @@ -51,15 +51,25 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; + let mut _7: std::ptr::Alignment; scope 8 { - scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } } scope 9 (inlined size_of_val_raw::<[T]>) { } - scope 10 (inlined align_of_val_raw::<[T]>) { + scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { + let _6: usize; + scope 11 { + scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { + scope 14 (inlined core::ub_checks::check_language_ub) { + scope 15 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + scope 12 (inlined align_of_val_raw::<[T]>) { + } } } } @@ -72,17 +82,17 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); + StorageLive(_7); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { + StorageLive(_6); _6 = const ::ALIGN; - StorageLive(_7); _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); StorageDead(_6); + _8 = copy (_7.0: std::ptr::alignment::AlignmentEnum); + StorageDead(_7); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir index 9202814adec7..6ffadd4c51db 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.32bit.panic-unwind.mir @@ -12,32 +12,32 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 3 { let _8: std::ptr::alignment::AlignmentEnum; scope 4 { - scope 12 (inlined Layout::size) { + scope 17 (inlined Layout::size) { } - scope 13 (inlined std::ptr::Unique::<[T]>::cast::) { - scope 14 (inlined NonNull::<[T]>::cast::) { - scope 15 (inlined NonNull::<[T]>::as_ptr) { + scope 18 (inlined std::ptr::Unique::<[T]>::cast::) { + scope 19 (inlined NonNull::<[T]>::cast::) { + scope 20 (inlined NonNull::<[T]>::as_ptr) { } } } - scope 16 (inlined as From>>::from) { - scope 17 (inlined std::ptr::Unique::::as_non_null_ptr) { + scope 21 (inlined as From>>::from) { + scope 22 (inlined std::ptr::Unique::::as_non_null_ptr) { } } - scope 18 (inlined ::deallocate) { - scope 19 (inlined std::alloc::Global::deallocate_impl) { - scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { + scope 23 (inlined ::deallocate) { + scope 24 (inlined std::alloc::Global::deallocate_impl) { + scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { let mut _9: *mut u8; - scope 21 (inlined Layout::size) { + scope 26 (inlined Layout::size) { } - scope 22 (inlined NonNull::::as_ptr) { + scope 27 (inlined NonNull::::as_ptr) { } - scope 23 (inlined std::alloc::dealloc) { + scope 28 (inlined std::alloc::dealloc) { let mut _10: usize; - scope 24 (inlined Layout::size) { + scope 29 (inlined Layout::size) { } - scope 25 (inlined Layout::align) { - scope 26 (inlined std::ptr::Alignment::as_usize) { + scope 30 (inlined Layout::align) { + scope 31 (inlined std::ptr::Alignment::as_usize) { } } } @@ -51,15 +51,25 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; + let mut _7: std::ptr::Alignment; scope 8 { - scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } } scope 9 (inlined size_of_val_raw::<[T]>) { } - scope 10 (inlined align_of_val_raw::<[T]>) { + scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { + let _6: usize; + scope 11 { + scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { + scope 14 (inlined core::ub_checks::check_language_ub) { + scope 15 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + scope 12 (inlined align_of_val_raw::<[T]>) { + } } } } @@ -72,17 +82,17 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); + StorageLive(_7); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { + StorageLive(_6); _6 = const ::ALIGN; - StorageLive(_7); _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); StorageDead(_6); + _8 = copy (_7.0: std::ptr::alignment::AlignmentEnum); + StorageDead(_7); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir index 9202814adec7..6ffadd4c51db 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-abort.mir @@ -12,32 +12,32 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 3 { let _8: std::ptr::alignment::AlignmentEnum; scope 4 { - scope 12 (inlined Layout::size) { + scope 17 (inlined Layout::size) { } - scope 13 (inlined std::ptr::Unique::<[T]>::cast::) { - scope 14 (inlined NonNull::<[T]>::cast::) { - scope 15 (inlined NonNull::<[T]>::as_ptr) { + scope 18 (inlined std::ptr::Unique::<[T]>::cast::) { + scope 19 (inlined NonNull::<[T]>::cast::) { + scope 20 (inlined NonNull::<[T]>::as_ptr) { } } } - scope 16 (inlined as From>>::from) { - scope 17 (inlined std::ptr::Unique::::as_non_null_ptr) { + scope 21 (inlined as From>>::from) { + scope 22 (inlined std::ptr::Unique::::as_non_null_ptr) { } } - scope 18 (inlined ::deallocate) { - scope 19 (inlined std::alloc::Global::deallocate_impl) { - scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { + scope 23 (inlined ::deallocate) { + scope 24 (inlined std::alloc::Global::deallocate_impl) { + scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { let mut _9: *mut u8; - scope 21 (inlined Layout::size) { + scope 26 (inlined Layout::size) { } - scope 22 (inlined NonNull::::as_ptr) { + scope 27 (inlined NonNull::::as_ptr) { } - scope 23 (inlined std::alloc::dealloc) { + scope 28 (inlined std::alloc::dealloc) { let mut _10: usize; - scope 24 (inlined Layout::size) { + scope 29 (inlined Layout::size) { } - scope 25 (inlined Layout::align) { - scope 26 (inlined std::ptr::Alignment::as_usize) { + scope 30 (inlined Layout::align) { + scope 31 (inlined std::ptr::Alignment::as_usize) { } } } @@ -51,15 +51,25 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; + let mut _7: std::ptr::Alignment; scope 8 { - scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } } scope 9 (inlined size_of_val_raw::<[T]>) { } - scope 10 (inlined align_of_val_raw::<[T]>) { + scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { + let _6: usize; + scope 11 { + scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { + scope 14 (inlined core::ub_checks::check_language_ub) { + scope 15 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + scope 12 (inlined align_of_val_raw::<[T]>) { + } } } } @@ -72,17 +82,17 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); + StorageLive(_7); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { + StorageLive(_6); _6 = const ::ALIGN; - StorageLive(_7); _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); StorageDead(_6); + _8 = copy (_7.0: std::ptr::alignment::AlignmentEnum); + StorageDead(_7); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir index 9202814adec7..6ffadd4c51db 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.generic_in_place.PreCodegen.after.64bit.panic-unwind.mir @@ -12,32 +12,32 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { scope 3 { let _8: std::ptr::alignment::AlignmentEnum; scope 4 { - scope 12 (inlined Layout::size) { + scope 17 (inlined Layout::size) { } - scope 13 (inlined std::ptr::Unique::<[T]>::cast::) { - scope 14 (inlined NonNull::<[T]>::cast::) { - scope 15 (inlined NonNull::<[T]>::as_ptr) { + scope 18 (inlined std::ptr::Unique::<[T]>::cast::) { + scope 19 (inlined NonNull::<[T]>::cast::) { + scope 20 (inlined NonNull::<[T]>::as_ptr) { } } } - scope 16 (inlined as From>>::from) { - scope 17 (inlined std::ptr::Unique::::as_non_null_ptr) { + scope 21 (inlined as From>>::from) { + scope 22 (inlined std::ptr::Unique::::as_non_null_ptr) { } } - scope 18 (inlined ::deallocate) { - scope 19 (inlined std::alloc::Global::deallocate_impl) { - scope 20 (inlined std::alloc::Global::deallocate_impl_runtime) { + scope 23 (inlined ::deallocate) { + scope 24 (inlined std::alloc::Global::deallocate_impl) { + scope 25 (inlined std::alloc::Global::deallocate_impl_runtime) { let mut _9: *mut u8; - scope 21 (inlined Layout::size) { + scope 26 (inlined Layout::size) { } - scope 22 (inlined NonNull::::as_ptr) { + scope 27 (inlined NonNull::::as_ptr) { } - scope 23 (inlined std::alloc::dealloc) { + scope 28 (inlined std::alloc::dealloc) { let mut _10: usize; - scope 24 (inlined Layout::size) { + scope 29 (inlined Layout::size) { } - scope 25 (inlined Layout::align) { - scope 26 (inlined std::ptr::Alignment::as_usize) { + scope 30 (inlined Layout::align) { + scope 31 (inlined std::ptr::Alignment::as_usize) { } } } @@ -51,15 +51,25 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { } scope 7 (inlined Layout::for_value_raw::<[T]>) { let mut _5: usize; - let mut _6: usize; + let mut _7: std::ptr::Alignment; scope 8 { - scope 11 (inlined #[track_caller] Layout::from_size_align_unchecked) { - let mut _7: std::ptr::Alignment; + scope 16 (inlined #[track_caller] Layout::from_size_alignment_unchecked) { } } scope 9 (inlined size_of_val_raw::<[T]>) { } - scope 10 (inlined align_of_val_raw::<[T]>) { + scope 10 (inlined std::ptr::Alignment::of_val_raw::<[T]>) { + let _6: usize; + scope 11 { + scope 13 (inlined #[track_caller] std::ptr::Alignment::new_unchecked) { + scope 14 (inlined core::ub_checks::check_language_ub) { + scope 15 (inlined core::ub_checks::check_language_ub::runtime) { + } + } + } + } + scope 12 (inlined align_of_val_raw::<[T]>) { + } } } } @@ -72,17 +82,17 @@ fn generic_in_place(_1: *mut Box<[T]>) -> () { StorageLive(_4); _3 = copy _2 as *mut [T] (Transmute); _4 = copy _2 as *const [T] (Transmute); - StorageLive(_6); + StorageLive(_7); _5 = std::intrinsics::size_of_val::<[T]>(move _4) -> [return: bb1, unwind unreachable]; } bb1: { + StorageLive(_6); _6 = const ::ALIGN; - StorageLive(_7); _7 = copy _6 as std::ptr::Alignment (Transmute); - _8 = move (_7.0: std::ptr::alignment::AlignmentEnum); - StorageDead(_7); StorageDead(_6); + _8 = copy (_7.0: std::ptr::alignment::AlignmentEnum); + StorageDead(_7); StorageDead(_4); switchInt(copy _5) -> [0: bb4, otherwise: bb2]; } diff --git a/tests/mir-opt/pre-codegen/drop_boxed_slice.rs b/tests/mir-opt/pre-codegen/drop_boxed_slice.rs index 83b10f8bd688..dddcffdd4843 100644 --- a/tests/mir-opt/pre-codegen/drop_boxed_slice.rs +++ b/tests/mir-opt/pre-codegen/drop_boxed_slice.rs @@ -11,7 +11,7 @@ pub unsafe fn generic_in_place(ptr: *mut Box<[T]>) { // CHECK: [[SIZE:_.+]] = std::intrinsics::size_of_val::<[T]> // CHECK: [[ALIGN:_.+]] = const ::ALIGN; // CHECK: [[B:_.+]] = copy [[ALIGN]] as std::ptr::Alignment (Transmute); - // CHECK: [[C:_.+]] = move ([[B]].0: std::ptr::alignment::AlignmentEnum); + // CHECK: [[C:_.+]] = copy ([[B]].0: std::ptr::alignment::AlignmentEnum); // CHECK: [[D:_.+]] = discriminant([[C]]); // CHECK: = alloc::alloc::__rust_dealloc({{.+}}, move [[SIZE]], move [[D]]) -> std::ptr::drop_in_place(ptr) diff --git a/tests/ui/thir-print/offset_of.stdout b/tests/ui/thir-print/offset_of.stdout index b3791a2446cb..5666a5972f37 100644 --- a/tests/ui/thir-print/offset_of.stdout +++ b/tests/ui/thir-print/offset_of.stdout @@ -68,7 +68,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::concrete).10) - span: $DIR/offset_of.rs:37:5: 1440:57 (#0) + span: $DIR/offset_of.rs:37:5: 1445:57 (#0) } } Stmt { @@ -117,7 +117,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::concrete).20) - span: $DIR/offset_of.rs:38:5: 1440:57 (#0) + span: $DIR/offset_of.rs:38:5: 1445:57 (#0) } } Stmt { @@ -166,7 +166,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::concrete).30) - span: $DIR/offset_of.rs:39:5: 1440:57 (#0) + span: $DIR/offset_of.rs:39:5: 1445:57 (#0) } } Stmt { @@ -215,7 +215,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::concrete).40) - span: $DIR/offset_of.rs:40:5: 1440:57 (#0) + span: $DIR/offset_of.rs:40:5: 1445:57 (#0) } } Stmt { @@ -264,7 +264,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::concrete).50) - span: $DIR/offset_of.rs:41:5: 1440:57 (#0) + span: $DIR/offset_of.rs:41:5: 1445:57 (#0) } } ] @@ -864,7 +864,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::generic).12) - span: $DIR/offset_of.rs:45:5: 1440:57 (#0) + span: $DIR/offset_of.rs:45:5: 1445:57 (#0) } } Stmt { @@ -913,7 +913,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::generic).24) - span: $DIR/offset_of.rs:46:5: 1440:57 (#0) + span: $DIR/offset_of.rs:46:5: 1445:57 (#0) } } Stmt { @@ -962,7 +962,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::generic).36) - span: $DIR/offset_of.rs:47:5: 1440:57 (#0) + span: $DIR/offset_of.rs:47:5: 1445:57 (#0) } } Stmt { @@ -1011,7 +1011,7 @@ body: ) else_block: None hir_id: HirId(DefId(offset_of::generic).48) - span: $DIR/offset_of.rs:48:5: 1440:57 (#0) + span: $DIR/offset_of.rs:48:5: 1445:57 (#0) } } ] From 10b6c5f5a72ad95bcad6325ca95fca91460f8bfa Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 26 Sep 2025 15:01:01 +0100 Subject: [PATCH 122/273] Move -Cembed-bitcode --- library/Cargo.toml | 18 ++++++++++++ src/bootstrap/src/core/build_steps/compile.rs | 7 ----- src/bootstrap/src/core/build_steps/dist.rs | 2 +- src/bootstrap/src/core/build_steps/test.rs | 5 ++-- src/bootstrap/src/core/builder/cargo.rs | 29 ++++++++++++++----- src/bootstrap/src/lib.rs | 10 +++++-- 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/library/Cargo.toml b/library/Cargo.toml index e30e62409428..2b664ec9bc82 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -54,6 +54,24 @@ rustflags = ["-Cpanic=abort"] [profile.release.package.panic_abort] rustflags = ["-Cpanic=abort"] +# The "dist" profile is used by bootstrap for prebuilt libstd artifacts +# These settings ensure that the prebuilt artifacts support a variety of features +# in the user's profile. +[profile.dist] +inherits = "release" +rustflags = [ + # Unconditionally embedding bitcode is necessary for when users enable LTO. + # Until Cargo can rebuild the standard library with the user profile's `lto` + # setting, Cargo will force this to be `no` + "-Cembed-bitcode=yes", +] + +[profile.dist.package.panic_abort] +rustflags = [ + "-Cpanic=abort", + "-Cembed-bitcode=yes", +] + [patch.crates-io] # See comments in `library/rustc-std-workspace-core/README.md` for what's going on here rustc-std-workspace-core = { path = 'rustc-std-workspace-core' } diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 651ff03a8690..2ea4bf96c86d 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -680,13 +680,6 @@ pub fn std_cargo( } } - // By default, rustc uses `-Cembed-bitcode=yes`, and Cargo overrides that - // with `-Cembed-bitcode=no` for non-LTO builds. However, libstd must be - // built with bitcode so that the produced rlibs can be used for both LTO - // builds (which use bitcode) and non-LTO builds (which use object code). - // So we override the override here! - cargo.rustflag("-Cembed-bitcode=yes"); - if builder.config.rust_lto == RustcLto::Off { cargo.rustflag("-Clto=off"); } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 8ba05a7fa3a7..84ab15781cdd 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -1008,7 +1008,7 @@ impl Step for Analysis { let src = builder .stage_out(compiler, Mode::Std) .join(target) - .join(builder.cargo_dir()) + .join(builder.cargo_dir(Mode::Std)) .join("deps") .join("save-analysis"); diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index f3a1c6b0e3dd..786c08cce17a 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -927,8 +927,9 @@ impl Step for Clippy { cargo.env("RUSTC_TEST_SUITE", builder.rustc(build_compiler)); cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(build_compiler)); - let host_libs = - builder.stage_out(build_compiler, Mode::ToolRustcPrivate).join(builder.cargo_dir()); + let host_libs = builder + .stage_out(build_compiler, Mode::ToolRustcPrivate) + .join(builder.cargo_dir(Mode::ToolRustcPrivate)); cargo.env("HOST_LIBS", host_libs); // Build the standard library that the tests can use. diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index dda0b40cb69e..6acffc1bf2c1 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -106,9 +106,9 @@ pub struct Cargo { rustdocflags: Rustflags, hostflags: HostFlags, allow_features: String, - release_build: bool, build_compiler_stage: u32, extra_rustflags: Vec, + profile: Option<&'static str>, } impl Cargo { @@ -137,7 +137,11 @@ impl Cargo { } pub fn release_build(&mut self, release_build: bool) { - self.release_build = release_build; + self.profile = if release_build { Some("release") } else { None }; + } + + pub fn profile(&mut self, profile: &'static str) { + self.profile = Some(profile); } pub fn compiler(&self) -> Compiler { @@ -407,8 +411,8 @@ impl Cargo { impl From for BootstrapCommand { fn from(mut cargo: Cargo) -> BootstrapCommand { - if cargo.release_build { - cargo.args.insert(0, "--release".into()); + if let Some(profile) = cargo.profile { + cargo.args.insert(0, format!("--profile={profile}").into()); } for arg in &cargo.extra_rustflags { @@ -1434,9 +1438,18 @@ impl Builder<'_> { .unwrap_or(&self.config.rust_rustflags) .clone(); - let release_build = self.config.rust_optimize.is_release() && - // cargo bench/install do not accept `--release` and miri doesn't want it - !matches!(cmd_kind, Kind::Bench | Kind::Install | Kind::Miri | Kind::MiriSetup | Kind::MiriTest); + let profile = + if matches!(cmd_kind, Kind::Bench | Kind::Miri | Kind::MiriSetup | Kind::MiriTest) { + // Use the default profile for bench/miri + None + } else { + match (mode, self.config.rust_optimize.is_release()) { + // Some std configuration exists in its own profile + (Mode::Std, true) => Some("dist"), + (_, true) => Some("release"), + (_, false) => Some("dev"), + } + }; Cargo { command: cargo, @@ -1448,9 +1461,9 @@ impl Builder<'_> { rustdocflags, hostflags, allow_features, - release_build, build_compiler_stage, extra_rustflags, + profile, } } } diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 857c0539e7d2..7af96bca44bd 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -898,8 +898,12 @@ impl Build { /// Component directory that Cargo will produce output into (e.g. /// release/debug) - fn cargo_dir(&self) -> &'static str { - if self.config.rust_optimize.is_release() { "release" } else { "debug" } + fn cargo_dir(&self, mode: Mode) -> &'static str { + match (mode, self.config.rust_optimize.is_release()) { + (Mode::Std, true) => "dist", + (_, true) => "release", + (_, false) => "debug", + } } fn tools_dir(&self, build_compiler: Compiler) -> PathBuf { @@ -956,7 +960,7 @@ impl Build { /// running a particular compiler, whether or not we're building the /// standard library, and targeting the specified architecture. fn cargo_out(&self, build_compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf { - self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir()) + self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir(mode)) } /// Root output directory of LLVM for `target` From 95c9586463937a9b5141ba7b7e6202af407c2a39 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 26 Sep 2025 15:24:44 +0100 Subject: [PATCH 123/273] Move frame pointers --- library/Cargo.toml | 5 +++++ src/bootstrap/src/core/build_steps/compile.rs | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/Cargo.toml b/library/Cargo.toml index 2b664ec9bc82..a1f64a4a3237 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -64,12 +64,17 @@ rustflags = [ # Until Cargo can rebuild the standard library with the user profile's `lto` # setting, Cargo will force this to be `no` "-Cembed-bitcode=yes", + # Enable frame pointers + "-Zunstable-options", + "-Cforce-frame-pointers=non-leaf", ] [profile.dist.package.panic_abort] rustflags = [ "-Cpanic=abort", "-Cembed-bitcode=yes", + "-Zunstable-options", + "-Cforce-frame-pointers=non-leaf", ] [patch.crates-io] diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 2ea4bf96c86d..7107140e1511 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -694,11 +694,6 @@ pub fn std_cargo( cargo.rustflag("-Cforce-unwind-tables=yes"); } - // Enable frame pointers by default for the library. Note that they are still controlled by a - // separate setting for the compiler. - cargo.rustflag("-Zunstable-options"); - cargo.rustflag("-Cforce-frame-pointers=non-leaf"); - let html_root = format!("-Zcrate-attr=doc(html_root_url=\"{}/\")", builder.doc_rust_lang_org_channel(),); cargo.rustflag(&html_root); From cabaed495acd6c968496fd4e0a72543b29ebbdad Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 30 Sep 2025 13:51:11 +0100 Subject: [PATCH 124/273] Move debuginfo=1 --- library/Cargo.toml | 2 ++ src/ci/run.sh | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Cargo.toml b/library/Cargo.toml index a1f64a4a3237..2a2c015811b8 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -59,6 +59,8 @@ rustflags = ["-Cpanic=abort"] # in the user's profile. [profile.dist] inherits = "release" +codegen-units = 1 +debug = 1 # "limited" rustflags = [ # Unconditionally embedding bitcode is necessary for when users enable LTO. # Until Cargo can rebuild the standard library with the user profile's `lto` diff --git a/src/ci/run.sh b/src/ci/run.sh index b486f0525f40..583ad4461bc3 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -120,8 +120,6 @@ if [ "$DEPLOY$DEPLOY_ALT" = "1" ]; then if [ "$DEPLOY_ALT" != "" ] && isLinux; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --debuginfo-level=2" - else - RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --debuginfo-level-std=1" fi if [ "$NO_LLVM_ASSERTIONS" = "1" ]; then From 1155a30c0e05dc8ed17f0466b655f46cd4d1d82d Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 30 Sep 2025 17:13:07 +0100 Subject: [PATCH 125/273] Move symbol mangling --- src/bootstrap/src/core/builder/cargo.rs | 34 ++++++++++--------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 6acffc1bf2c1..041217e1dc8c 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -664,23 +664,15 @@ impl Builder<'_> { rustflags.arg(sysroot_str); } - let use_new_symbol_mangling = match self.config.rust_new_symbol_mangling { - Some(setting) => { - // If an explicit setting is given, use that - setting + let use_new_symbol_mangling = self.config.rust_new_symbol_mangling.or_else(|| { + if mode != Mode::Std { + // The compiler and tools default to the new scheme + Some(true) + } else { + // std follows the flag's default, which per compiler-team#938 is v0 on nightly + None } - // Per compiler-team#938, v0 mangling is used on nightly - None if self.config.channel == "dev" || self.config.channel == "nightly" => true, - None => { - if mode == Mode::Std { - // The standard library defaults to the legacy scheme - false - } else { - // The compiler and tools default to the new scheme - true - } - } - }; + }); // By default, windows-rs depends on a native library that doesn't get copied into the // sysroot. Passing this cfg enables raw-dylib support instead, which makes the native @@ -691,11 +683,11 @@ impl Builder<'_> { rustflags.arg("--cfg=windows_raw_dylib"); } - if use_new_symbol_mangling { - rustflags.arg("-Csymbol-mangling-version=v0"); - } else { - rustflags.arg("-Csymbol-mangling-version=legacy"); - } + rustflags.arg(match use_new_symbol_mangling { + Some(true) => "-Csymbol-mangling-version=v0", + Some(false) => "-Csymbol-mangling-version=legacy", + None => "", + }); // Always enable move/copy annotations for profiler visibility (non-stage0 only). // Note that -Zannotate-moves is only effective with debugging info enabled. From dae8ea92a7337b3c2c4db7d06de554fbcf2de2e8 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Mon, 6 Oct 2025 16:53:54 +0100 Subject: [PATCH 126/273] Configure test's unstable feature gate when built outside of bootstrap This uses a build probe to figure out the current toolchain version - the only alternative to this is bespoke logic in Cargo to tell `test` when the toolchain is stable/unstable. The behaviour should be the same as when using `CFG_DISABLE_UNSTABLE_FEATURES` unless an arbitrary channel is provided to bootstrap, which I believe necessitates a fork anyway. --- library/test/build.rs | 11 +++++++++++ library/test/src/cli.rs | 11 +++++------ src/bootstrap/src/core/build_steps/compile.rs | 6 ------ 3 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 library/test/build.rs diff --git a/library/test/build.rs b/library/test/build.rs new file mode 100644 index 000000000000..a2bc8936b519 --- /dev/null +++ b/library/test/build.rs @@ -0,0 +1,11 @@ +fn main() { + println!("cargo:rustc-check-cfg=cfg(enable_unstable_features)"); + + let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".into()); + let version = std::process::Command::new(rustc).arg("-vV").output().unwrap(); + let stdout = String::from_utf8(version.stdout).unwrap(); + + if stdout.contains("nightly") || stdout.contains("dev") { + println!("cargo:rustc-cfg=enable_unstable_features"); + } +} diff --git a/library/test/src/cli.rs b/library/test/src/cli.rs index 35291cc15c91..172785936b20 100644 --- a/library/test/src/cli.rs +++ b/library/test/src/cli.rs @@ -314,15 +314,14 @@ fn parse_opts_impl(matches: getopts::Matches) -> OptRes { Ok(test_opts) } -// FIXME: Copied from librustc_ast until linkage errors are resolved. Issue #47566 fn is_nightly() -> bool { - // Whether this is a feature-staged build, i.e., on the beta or stable channel - let disable_unstable_features = - option_env!("CFG_DISABLE_UNSTABLE_FEATURES").map(|s| s != "0").unwrap_or(false); - // Whether we should enable unstable features for bootstrapping + // Whether the current rustc version should allow unstable features + let enable_unstable_features = cfg!(enable_unstable_features); + + // The runtime override for unstable features let bootstrap = env::var("RUSTC_BOOTSTRAP").is_ok(); - bootstrap || !disable_unstable_features + bootstrap || enable_unstable_features } // Gets the CLI options associated with `report-time` feature. diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 7107140e1511..405ab9f6eaa2 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -626,12 +626,6 @@ pub fn std_cargo( CompilerBuiltins::BuildRustOnly => "", }; - // `libtest` uses this to know whether or not to support - // `-Zunstable-options`. - if !builder.unstable_features() { - cargo.env("CFG_DISABLE_UNSTABLE_FEATURES", "1"); - } - for krate in crates { cargo.args(["-p", krate]); } From 539a045dfbf846692c8539e42a1d98efefa2fae9 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Thu, 8 Jan 2026 16:22:26 +0000 Subject: [PATCH 127/273] Rephrase embed-bitcode comment --- library/Cargo.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/Cargo.toml b/library/Cargo.toml index 2a2c015811b8..b26e5f41c931 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -62,9 +62,10 @@ inherits = "release" codegen-units = 1 debug = 1 # "limited" rustflags = [ - # Unconditionally embedding bitcode is necessary for when users enable LTO. - # Until Cargo can rebuild the standard library with the user profile's `lto` - # setting, Cargo will force this to be `no` + # `profile.lto=off` implies `-Cembed-bitcode=no`, but unconditionally embedding + # bitcode is necessary for when users enable LTO. + # Required until Cargo can re-build the standard library based on the value + # of `profile.lto` in the user's profile. "-Cembed-bitcode=yes", # Enable frame pointers "-Zunstable-options", From 733074a6bb95384c533e872ea7afe74be5996388 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Thu, 8 Jan 2026 15:52:50 +0000 Subject: [PATCH 128/273] Don't use debug profile for std when rust.optimize=false --- src/bootstrap/src/core/build_steps/tool.rs | 2 +- src/bootstrap/src/core/builder/cargo.rs | 13 +++++++++---- src/bootstrap/src/lib.rs | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index a90687c5c0f9..d86159d3cc7e 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -133,7 +133,7 @@ impl Step for ToolBuild { RustcLto::ThinLocal => None, }; if let Some(lto) = lto { - cargo.env(cargo_profile_var("LTO", &builder.config), lto); + cargo.env(cargo_profile_var("LTO", &builder.config, self.mode), lto); } } diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 041217e1dc8c..b36fcd990dc6 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -602,7 +602,7 @@ impl Builder<'_> { build_stamp::clear_if_dirty(self, &my_out, &rustdoc); } - let profile_var = |name: &str| cargo_profile_var(name, &self.config); + let profile_var = |name: &str| cargo_profile_var(name, &self.config, mode); // See comment in rustc_llvm/build.rs for why this is necessary, largely llvm-config // needs to not accidentally link to libLLVM in stage0/lib. @@ -1437,7 +1437,7 @@ impl Builder<'_> { } else { match (mode, self.config.rust_optimize.is_release()) { // Some std configuration exists in its own profile - (Mode::Std, true) => Some("dist"), + (Mode::Std, _) => Some("dist"), (_, true) => Some("release"), (_, false) => Some("dev"), } @@ -1460,7 +1460,12 @@ impl Builder<'_> { } } -pub fn cargo_profile_var(name: &str, config: &Config) -> String { - let profile = if config.rust_optimize.is_release() { "RELEASE" } else { "DEV" }; +pub fn cargo_profile_var(name: &str, config: &Config, mode: Mode) -> String { + let profile = match (mode, config.rust_optimize.is_release()) { + // Some std configuration exists in its own profile + (Mode::Std, _) => "DIST", + (_, true) => "RELEASE", + (_, false) => "DEV", + }; format!("CARGO_PROFILE_{profile}_{name}") } diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 7af96bca44bd..dfa29b5aa3cd 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -900,7 +900,7 @@ impl Build { /// release/debug) fn cargo_dir(&self, mode: Mode) -> &'static str { match (mode, self.config.rust_optimize.is_release()) { - (Mode::Std, true) => "dist", + (Mode::Std, _) => "dist", (_, true) => "release", (_, false) => "debug", } From f4bd4b1692a4d81991d148092e6f8453129d48b5 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 9 Jan 2026 17:00:35 +0000 Subject: [PATCH 129/273] Avoid passing empty string to rustflags --- src/bootstrap/src/core/builder/cargo.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index b36fcd990dc6..7150b2b0d59f 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -683,11 +683,13 @@ impl Builder<'_> { rustflags.arg("--cfg=windows_raw_dylib"); } - rustflags.arg(match use_new_symbol_mangling { - Some(true) => "-Csymbol-mangling-version=v0", - Some(false) => "-Csymbol-mangling-version=legacy", - None => "", - }); + if let Some(usm) = use_new_symbol_mangling { + rustflags.arg(if usm { + "-Csymbol-mangling-version=v0" + } else { + "-Csymbol-mangling-version=legacy" + }); + } // Always enable move/copy annotations for profiler visibility (non-stage0 only). // Note that -Zannotate-moves is only effective with debugging info enabled. From 368504e590e4a782668c535ec87c321acd8bfe04 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 9 Jan 2026 18:03:41 +0000 Subject: [PATCH 130/273] Readd CI debuginfo-level-std directive as bootstrap has to set a level based on the default values of rust.debug[...] keys --- src/ci/run.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ci/run.sh b/src/ci/run.sh index 583ad4461bc3..b486f0525f40 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -120,6 +120,8 @@ if [ "$DEPLOY$DEPLOY_ALT" = "1" ]; then if [ "$DEPLOY_ALT" != "" ] && isLinux; then RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --debuginfo-level=2" + else + RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --debuginfo-level-std=1" fi if [ "$NO_LLVM_ASSERTIONS" = "1" ]; then From dd9241d150d6645ea61c65563ff38e685a2f906b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 27 Dec 2025 17:17:54 +0100 Subject: [PATCH 131/273] `c_variadic`: use `Clone` instead of LLVM `va_copy` --- .../src/intrinsics/mod.rs | 2 +- .../rustc_codegen_gcc/src/intrinsic/mod.rs | 3 - compiler/rustc_codegen_llvm/src/intrinsic.rs | 8 -- .../rustc_hir_analysis/src/check/intrinsic.rs | 12 +-- library/core/src/ffi/va_list.rs | 83 +++++++++++++------ library/core/src/intrinsics/mod.rs | 27 +++--- tests/codegen-llvm/cffi/c-variadic-copy.rs | 16 ---- tests/codegen-llvm/cffi/c-variadic-opt.rs | 11 --- tests/ui/c-variadic/copy.rs | 24 ++++++ 9 files changed, 101 insertions(+), 85 deletions(-) delete mode 100644 tests/codegen-llvm/cffi/c-variadic-copy.rs create mode 100644 tests/ui/c-variadic/copy.rs diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index a78c6e0a4e7a..ab9a11305baa 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -1506,7 +1506,7 @@ fn codegen_regular_intrinsic_call<'tcx>( } // FIXME implement variadics in cranelift - sym::va_copy | sym::va_arg | sym::va_end => { + sym::va_arg | sym::va_end => { fx.tcx.dcx().span_fatal( source_info.span, "Defining variadic functions is not yet supported by Cranelift", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 36ea76cbc51a..553e4d3d2fe0 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -391,9 +391,6 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc sym::breakpoint => { unimplemented!(); } - sym::va_copy => { - unimplemented!(); - } sym::va_arg => { unimplemented!(); } diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index c6aae89f1e51..83a256e33e12 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -269,14 +269,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { return Ok(()); } sym::breakpoint => self.call_intrinsic("llvm.debugtrap", &[], &[]), - sym::va_copy => { - let dest = args[0].immediate(); - self.call_intrinsic( - "llvm.va_copy", - &[self.val_ty(dest)], - &[dest, args[1].immediate()], - ) - } sym::va_arg => { match result.layout.backend_repr { BackendRepr::Scalar(scalar) => { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index d3d167f6e254..98f9548058f4 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -216,6 +216,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_name | sym::type_of | sym::ub_checks + | sym::va_copy | sym::variant_count | sym::vtable_for | sym::wrapping_add @@ -629,14 +630,13 @@ pub(crate) fn check_intrinsic_type( ) } - sym::va_start | sym::va_end => { - (0, 0, vec![mk_va_list_ty(hir::Mutability::Mut).0], tcx.types.unit) - } - sym::va_copy => { let (va_list_ref_ty, va_list_ty) = mk_va_list_ty(hir::Mutability::Not); - let va_list_ptr_ty = Ty::new_mut_ptr(tcx, va_list_ty); - (0, 0, vec![va_list_ptr_ty, va_list_ref_ty], tcx.types.unit) + (0, 0, vec![va_list_ref_ty], va_list_ty) + } + + sym::va_start | sym::va_end => { + (0, 0, vec![mk_va_list_ty(hir::Mutability::Mut).0], tcx.types.unit) } sym::va_arg => (1, 0, vec![mk_va_list_ty(hir::Mutability::Mut).0], param(0)), diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index d5166baf0c7c..390062190707 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -34,6 +34,10 @@ use crate::marker::PhantomCovariantLifetime; // // The Clang `BuiltinVaListKind` enumerates the `va_list` variations that Clang supports, // and we mirror these here. +// +// For all current LLVM targets, `va_copy` lowers to `memcpy`. Hence the inner structs below all +// derive `Copy`. However, in the future we might want to support a target where `va_copy` +// allocates, or otherwise violates the requirements of `Copy`. Therefore `VaList` is only `Clone`. crate::cfg_select! { all( target_arch = "aarch64", @@ -45,10 +49,12 @@ crate::cfg_select! { /// /// See the [AArch64 Procedure Call Standard] for more details. /// + /// `va_copy` is `memcpy`: + /// /// [AArch64 Procedure Call Standard]: /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf #[repr(C)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] struct VaListInner { stack: *const c_void, gr_top: *const c_void, @@ -62,11 +68,13 @@ crate::cfg_select! { /// /// See the [LLVM source] and [GCC header] for more details. /// + /// `va_copy` is `memcpy`: + /// /// [LLVM source]: /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111 /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h #[repr(C)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] #[rustc_pass_indirectly_in_non_rustic_abis] struct VaListInner { gpr: u8, @@ -81,10 +89,12 @@ crate::cfg_select! { /// /// See the [S/390x ELF Application Binary Interface Supplement] for more details. /// + /// `va_copy` is `memcpy`: + /// /// [S/390x ELF Application Binary Interface Supplement]: /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf #[repr(C)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] #[rustc_pass_indirectly_in_non_rustic_abis] struct VaListInner { gpr: i64, @@ -98,10 +108,13 @@ crate::cfg_select! { /// /// See the [System V AMD64 ABI] for more details. /// + /// `va_copy` is `memcpy`: + /// (github won't render that file, look for `SDValue LowerVACOPY`) + /// /// [System V AMD64 ABI]: /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf #[repr(C)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] #[rustc_pass_indirectly_in_non_rustic_abis] struct VaListInner { gp_offset: i32, @@ -115,10 +128,12 @@ crate::cfg_select! { /// /// See the [LLVM source] for more details. /// + /// `va_copy` is `memcpy`: + /// /// [LLVM source]: /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215 #[repr(C)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] #[rustc_pass_indirectly_in_non_rustic_abis] struct VaListInner { stk: *const i32, @@ -132,10 +147,12 @@ crate::cfg_select! { /// /// See the [LLVM source] for more details. On bare metal Hexagon uses an opaque pointer. /// + /// `va_copy` is `memcpy`: + /// /// [LLVM source]: /// https://github.com/llvm/llvm-project/blob/0cdc1b6dd4a870fc41d4b15ad97e0001882aba58/clang/lib/CodeGen/Targets/Hexagon.cpp#L407-L417 #[repr(C)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] #[rustc_pass_indirectly_in_non_rustic_abis] struct VaListInner { __current_saved_reg_area_pointer: *const c_void, @@ -156,8 +173,10 @@ crate::cfg_select! { // That pointer is probably just the next variadic argument on the caller's stack. _ => { /// Basic implementation of a `va_list`. + /// + /// `va_copy` is `memcpy`: #[repr(transparent)] - #[derive(Debug)] + #[derive(Debug, Clone, Copy)] struct VaListInner { ptr: *const c_void, } @@ -179,6 +198,36 @@ impl fmt::Debug for VaList<'_> { } } +impl VaList<'_> { + // Helper used in the implementation of the `va_copy` intrinsic. + pub(crate) fn duplicate(&self) -> Self { + Self { inner: self.inner.clone(), _marker: self._marker } + } +} + +impl Clone for VaList<'_> { + #[inline] + fn clone(&self) -> Self { + // We only implement Clone and not Copy because some future target might not be able to + // implement Copy (e.g. because it allocates). For the same reason we use an intrinsic + // to do the copying: the fact that on all current targets, this is just `memcpy`, is an implementation + // detail. The intrinsic lets Miri catch UB from code incorrectly relying on that implementation detail. + va_copy(self) + } +} + +impl Drop for VaList<'_> { + fn drop(&mut self) { + // For all current LLVM targets `va_end` is a no-op. + // + // We implement `Drop` here because some future target might need to actually run + // destructors (e.g. to deallocate). + // + // Rust requires that not calling `va_end` on a `va_list` does not cause undefined + // behaviour: it is safe to leak values. + } +} + mod sealed { pub trait Sealed {} @@ -253,26 +302,6 @@ impl<'f> VaList<'f> { } } -impl<'f> Clone for VaList<'f> { - #[inline] - fn clone(&self) -> Self { - let mut dest = crate::mem::MaybeUninit::uninit(); - // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal. - unsafe { - va_copy(dest.as_mut_ptr(), self); - dest.assume_init() - } - } -} - -impl<'f> Drop for VaList<'f> { - fn drop(&mut self) { - // Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour - // (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this - // destructor is empty. - } -} - // Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current // target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`. const _: () = { diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 8d112b4c5d18..6d231faf38f7 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3451,19 +3451,6 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize ) } -/// Copies the current location of arglist `src` to the arglist `dst`. -/// -/// # Safety -/// -/// You must check the following invariants before you call this function: -/// -/// - `dest` must be non-null and point to valid, writable memory. -/// - `dest` must not alias `src`. -/// -#[rustc_intrinsic] -#[rustc_nounwind] -pub unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>); - /// Loads an argument of type `T` from the `va_list` `ap` and increment the /// argument `ap` points to. /// @@ -3482,6 +3469,20 @@ pub unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>); #[rustc_nounwind] pub unsafe fn va_arg(ap: &mut VaList<'_>) -> T; +/// Duplicates a variable argument list. The returned list is initially at the same position as +/// the one in `src`, but can be advanced independently. +/// +/// Codegen backends should not have custom behavior for this intrinsic, they should always use +/// this fallback implementation. This intrinsic *does not* map to the LLVM `va_copy` intrinsic. +/// +/// This intrinsic exists only as a hook for Miri and constant evaluation, and is used to detect UB +/// when a variable argument list is used incorrectly. +#[rustc_intrinsic] +#[rustc_nounwind] +pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { + src.duplicate() +} + /// Destroy the arglist `ap` after initialization with `va_start` or `va_copy`. /// /// # Safety diff --git a/tests/codegen-llvm/cffi/c-variadic-copy.rs b/tests/codegen-llvm/cffi/c-variadic-copy.rs deleted file mode 100644 index 0cbdcb4bbb85..000000000000 --- a/tests/codegen-llvm/cffi/c-variadic-copy.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Tests that `VaList::clone` gets inlined into a call to `llvm.va_copy` - -#![crate_type = "lib"] -#![feature(c_variadic)] -#![no_std] -use core::ffi::VaList; - -extern "C" { - fn foreign_c_variadic_1(_: VaList, ...); -} - -pub unsafe extern "C" fn clone_variadic(ap: VaList) { - let mut ap2 = ap.clone(); - // CHECK: call void @llvm.va_copy - foreign_c_variadic_1(ap2, 42i32); -} diff --git a/tests/codegen-llvm/cffi/c-variadic-opt.rs b/tests/codegen-llvm/cffi/c-variadic-opt.rs index 3cc0c3e9f9bd..c779b25450fd 100644 --- a/tests/codegen-llvm/cffi/c-variadic-opt.rs +++ b/tests/codegen-llvm/cffi/c-variadic-opt.rs @@ -17,14 +17,3 @@ pub unsafe extern "C" fn c_variadic_no_use(fmt: *const i8, mut ap: ...) -> i32 { vprintf(fmt, ap) // CHECK: call void @llvm.va_end } - -// Check that `VaList::clone` gets inlined into a direct call to `llvm.va_copy` -#[no_mangle] -pub unsafe extern "C" fn c_variadic_clone(fmt: *const i8, mut ap: ...) -> i32 { - // CHECK: call void @llvm.va_start - let mut ap2 = ap.clone(); - // CHECK: call void @llvm.va_copy - let res = vprintf(fmt, ap2); - res - // CHECK: call void @llvm.va_end -} diff --git a/tests/ui/c-variadic/copy.rs b/tests/ui/c-variadic/copy.rs new file mode 100644 index 000000000000..e9171738aa15 --- /dev/null +++ b/tests/ui/c-variadic/copy.rs @@ -0,0 +1,24 @@ +//@ run-pass +//@ ignore-backends: gcc +#![feature(c_variadic)] + +// Test the behavior of `VaList::clone`. In C a `va_list` is duplicated using `va_copy`, but the +// rust api just uses `Clone`. This should create a completely independent cursor into the +// variable argument list: advancing the original has no effect on the copy and vice versa. + +fn main() { + unsafe { variadic(1, 2, 3) } +} + +unsafe extern "C" fn variadic(mut ap1: ...) { + let mut ap2 = ap1.clone(); + + assert_eq!(ap1.arg::(), 1); + assert_eq!(ap2.arg::(), 1); + + assert_eq!(ap2.arg::(), 2); + assert_eq!(ap1.arg::(), 2); + + drop(ap1); + assert_eq!(ap2.arg::(), 3); +} From 922057cfa663174a1cbf22e05aa6b0475cbaa79f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 9 Jan 2026 17:34:55 +0100 Subject: [PATCH 132/273] c-variadic: make `VaList::drop` call the rust `va_end` --- library/core/src/ffi/va_list.rs | 13 ++++--------- library/core/src/intrinsics/mod.rs | 13 +++++++++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 390062190707..d0f155316a10 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -5,7 +5,7 @@ #[cfg(not(target_arch = "xtensa"))] use crate::ffi::c_void; use crate::fmt; -use crate::intrinsics::{va_arg, va_copy}; +use crate::intrinsics::{va_arg, va_copy, va_end}; use crate::marker::PhantomCovariantLifetime; // There are currently three flavors of how a C `va_list` is implemented for @@ -216,15 +216,10 @@ impl Clone for VaList<'_> { } } -impl Drop for VaList<'_> { +impl<'f> Drop for VaList<'f> { fn drop(&mut self) { - // For all current LLVM targets `va_end` is a no-op. - // - // We implement `Drop` here because some future target might need to actually run - // destructors (e.g. to deallocate). - // - // Rust requires that not calling `va_end` on a `va_list` does not cause undefined - // behaviour: it is safe to leak values. + // SAFETY: this variable argument list is being dropped, so won't be read from again. + unsafe { va_end(self) } } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 6d231faf38f7..b134c0ba4c8b 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3483,7 +3483,14 @@ pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { src.duplicate() } -/// Destroy the arglist `ap` after initialization with `va_start` or `va_copy`. +/// Destroy the variable argument list `ap` after initialization with `va_start` (part of the +/// desugaring of `...`) or `va_copy`. +/// +/// Code generation backends should not provide a custom implementation for this intrinsic. This +/// intrinsic *does not* map to the LLVM `va_end` intrinsic. +/// +/// This function is a no-op on all current targets, but used as a hook for const evaluation to +/// detect UB when a variable argument list is used incorrectly. /// /// # Safety /// @@ -3491,4 +3498,6 @@ pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_end(ap: &mut VaList<'_>); +pub unsafe fn va_end(ap: &mut VaList<'_>) { + /* deliberately does nothing */ +} From 9304071904374a1e29bac4ae65116347381d1505 Mon Sep 17 00:00:00 2001 From: Meng Xu Date: Tue, 20 Jan 2026 13:05:41 -0500 Subject: [PATCH 133/273] missing colon after the compile-flags directive --- tests/ui/instrument-coverage/coverage-options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/instrument-coverage/coverage-options.rs b/tests/ui/instrument-coverage/coverage-options.rs index ead2e3221d8c..091ccdf38a08 100644 --- a/tests/ui/instrument-coverage/coverage-options.rs +++ b/tests/ui/instrument-coverage/coverage-options.rs @@ -1,5 +1,5 @@ //@ revisions: block branch condition bad -//@ compile-flags -Cinstrument-coverage -Zno-profiler-runtime +//@ compile-flags: -Cinstrument-coverage -Zno-profiler-runtime //@ [block] check-pass //@ [block] compile-flags: -Zcoverage-options=block From c3205f98dda8ab49d4774f8af61211d75ae7b2d4 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Fri, 7 Nov 2025 00:22:14 -0600 Subject: [PATCH 134/273] Replace #[rustc_do_not_implement_via_object] with #[rustc_dyn_incompatible_trait], which makes the marked trait dyn-incompatible. Removes the attribute from `MetaSized` and `PointeeSized`, with a special case in the trait solvers for `MetaSized`. `dyn MetaSized` is a perfectly cromulent type, and seems to only have had #[rustc_do_not_implement_via_object] so the builtin object candidate does not overlap with the builtin MetaSized impl that all `dyn` types get. Resolves this with a special case by checking `is_sizedness_trait` where the trait solvers previously checked `implement_via_object`. `dyn PointeeSized` alone is rejected for other reasons (since `dyn PointeeSized` is considered to have no principal trait because `PointeeSized` is removed at an earlier stage of the compiler), but `(dyn PointeeSized + Send)` is valid and equivalent to `dyn Send`. Add suggestions from code review Update compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs and tests Co-authored-by: lcnr --- .../src/attributes/traits.rs | 8 +- compiler/rustc_attr_parsing/src/context.rs | 6 +- compiler/rustc_feature/src/builtin_attrs.rs | 6 +- .../rustc_hir/src/attrs/data_structures.rs | 6 +- .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 +- .../rustc_hir_analysis/src/coherence/mod.rs | 4 +- compiler/rustc_hir_analysis/src/collect.rs | 5 +- compiler/rustc_middle/src/traits/mod.rs | 8 ++ compiler/rustc_middle/src/ty/context.rs | 4 - compiler/rustc_middle/src/ty/trait_def.rs | 8 +- .../src/solve/assembly/mod.rs | 4 +- compiler/rustc_passes/src/check_attr.rs | 2 +- compiler/rustc_public/src/ty.rs | 2 +- .../src/unstable/convert/stable/ty.rs | 2 +- compiler/rustc_span/src/symbol.rs | 2 +- .../src/traits/dyn_compatibility.rs | 3 + .../src/traits/project.rs | 4 - .../src/traits/select/candidate_assembly.rs | 4 +- compiler/rustc_type_ir/src/interner.rs | 2 - library/core/src/marker.rs | 16 ++-- library/core/src/mem/transmutability.rs | 2 +- library/core/src/ptr/metadata.rs | 2 +- tests/ui/dyn-compatibility/metasized.rs | 25 ++++++ tests/ui/dyn-compatibility/pointeesized.rs | 17 ++++ tests/ui/dyn-compatibility/sized-3.rs | 29 ++++++ tests/ui/dyn-compatibility/sized-3.stderr | 88 +++++++++++++++++++ .../deny-builtin-object-impl.current.stderr | 74 +++++++++++----- .../deny-builtin-object-impl.next.stderr | 74 +++++++++++----- tests/ui/traits/deny-builtin-object-impl.rs | 13 +-- .../ui/traits/ice-with-dyn-pointee-errors.rs | 4 +- .../traits/ice-with-dyn-pointee-errors.stderr | 46 +++++++--- tests/ui/traits/ice-with-dyn-pointee.rs | 11 --- tests/ui/unsized/issue-71659.current.stderr | 28 +++--- tests/ui/unsized/issue-71659.next.stderr | 28 +++--- tests/ui/unsized/issue-71659.rs | 2 +- 35 files changed, 387 insertions(+), 154 deletions(-) create mode 100644 tests/ui/dyn-compatibility/metasized.rs create mode 100644 tests/ui/dyn-compatibility/pointeesized.rs create mode 100644 tests/ui/dyn-compatibility/sized-3.rs create mode 100644 tests/ui/dyn-compatibility/sized-3.stderr delete mode 100644 tests/ui/traits/ice-with-dyn-pointee.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index ee5895a6efd0..c0db5b4d442a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -94,12 +94,12 @@ impl NoArgsAttributeParser for DenyExplicitImplParser { const CREATE: fn(Span) -> AttributeKind = AttributeKind::DenyExplicitImpl; } -pub(crate) struct DoNotImplementViaObjectParser; -impl NoArgsAttributeParser for DoNotImplementViaObjectParser { - const PATH: &[Symbol] = &[sym::rustc_do_not_implement_via_object]; +pub(crate) struct DynIncompatibleTraitParser; +impl NoArgsAttributeParser for DynIncompatibleTraitParser { + const PATH: &[Symbol] = &[sym::rustc_dyn_incompatible_trait]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); - const CREATE: fn(Span) -> AttributeKind = AttributeKind::DoNotImplementViaObject; + const CREATE: fn(Span) -> AttributeKind = AttributeKind::DynIncompatibleTrait; } // Specialization diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 6aae2b90a504..e6d91e936e63 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -88,8 +88,8 @@ use crate::attributes::stability::{ use crate::attributes::test_attrs::{IgnoreParser, ShouldPanicParser}; use crate::attributes::traits::{ AllowIncoherentImplParser, CoinductiveParser, DenyExplicitImplParser, - DoNotImplementViaObjectParser, FundamentalParser, MarkerParser, ParenSugarParser, - PointeeParser, SkipDuringMethodDispatchParser, SpecializationTraitParser, TypeConstParser, + DynIncompatibleTraitParser, FundamentalParser, MarkerParser, ParenSugarParser, PointeeParser, + SkipDuringMethodDispatchParser, SpecializationTraitParser, TypeConstParser, UnsafeSpecializationMarkerParser, }; use crate::attributes::transparency::TransparencyParser; @@ -254,7 +254,7 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 97dc9dd21a65..ded8a5a4ae51 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1315,13 +1315,13 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ "`#[rustc_deny_explicit_impl]` enforces that a trait can have no user-provided impls" ), rustc_attr!( - rustc_do_not_implement_via_object, + rustc_dyn_incompatible_trait, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No, - "`#[rustc_do_not_implement_via_object]` opts out of the automatic trait impl for trait objects \ - (`impl Trait for dyn Trait`)" + "`#[rustc_dyn_incompatible_trait]` marks a trait as dyn-incompatible, \ + even if it otherwise satisfies the requirements to be dyn-compatible." ), rustc_attr!( rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 8d2baae703b8..3f0546e800fa 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -728,9 +728,6 @@ pub enum AttributeKind { /// Represents [`#[deprecated]`](https://doc.rust-lang.org/stable/reference/attributes/diagnostics.html#the-deprecated-attribute). Deprecation { deprecation: Deprecation, span: Span }, - /// Represents `#[rustc_do_not_implement_via_object]`. - DoNotImplementViaObject(Span), - /// Represents `#[diagnostic::do_not_recommend]`. DoNotRecommend { attr_span: Span }, @@ -746,6 +743,9 @@ pub enum AttributeKind { /// Represents `#[rustc_dummy]`. Dummy, + /// Represents `#[rustc_dyn_incompatible_trait]`. + DynIncompatibleTrait(Span), + /// Implementation detail of `#[eii]` EiiDeclaration(EiiDecl), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index fd764ad458b1..991c7003cb09 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -43,11 +43,11 @@ impl AttributeKind { DebuggerVisualizer(..) => No, DenyExplicitImpl(..) => No, Deprecation { .. } => Yes, - DoNotImplementViaObject(..) => No, DoNotRecommend { .. } => Yes, Doc(_) => Yes, DocComment { .. } => Yes, Dummy => No, + DynIncompatibleTrait(..) => No, EiiDeclaration(_) => Yes, EiiForeignItem => No, EiiImpls(..) => No, diff --git a/compiler/rustc_hir_analysis/src/coherence/mod.rs b/compiler/rustc_hir_analysis/src/coherence/mod.rs index bc3231cff9b4..58483dc44fe9 100644 --- a/compiler/rustc_hir_analysis/src/coherence/mod.rs +++ b/compiler/rustc_hir_analysis/src/coherence/mod.rs @@ -211,9 +211,7 @@ fn check_object_overlap<'tcx>( // This is a WF error tested by `coherence-impl-trait-for-trait-dyn-compatible.rs`. } else { let mut supertrait_def_ids = elaborate::supertrait_def_ids(tcx, component_def_id); - if supertrait_def_ids - .any(|d| d == trait_def_id && tcx.trait_def(d).implement_via_object) - { + if supertrait_def_ids.any(|d| d == trait_def_id) { let span = tcx.def_span(impl_def_id); return Err(struct_span_code_err!( tcx.dcx(), diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 9c0b638c1482..dd399f9d90de 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -924,7 +924,8 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { ); let deny_explicit_impl = find_attr!(attrs, AttributeKind::DenyExplicitImpl(_)); - let implement_via_object = !find_attr!(attrs, AttributeKind::DoNotImplementViaObject(_)); + let force_dyn_incompatible = + find_attr!(attrs, AttributeKind::DynIncompatibleTrait(span) => *span); ty::TraitDef { def_id: def_id.to_def_id(), @@ -939,7 +940,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { skip_boxed_slice_during_method_dispatch, specialization_kind, must_implement_one_of, - implement_via_object, + force_dyn_incompatible, deny_explicit_impl, } } diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index c064313f67b2..d71df36684f5 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -764,6 +764,9 @@ pub enum DynCompatibilityViolation { /// `Self: Sized` declared on the trait. SizedSelf(SmallVec<[Span; 1]>), + /// Trait is marked `#[rustc_dyn_incompatible_trait]`. + ExplicitlyDynIncompatible(SmallVec<[Span; 1]>), + /// Supertrait reference references `Self` an in illegal location /// (e.g., `trait Foo : Bar`). SupertraitSelf(SmallVec<[Span; 1]>), @@ -788,6 +791,9 @@ impl DynCompatibilityViolation { pub fn error_msg(&self) -> Cow<'static, str> { match self { DynCompatibilityViolation::SizedSelf(_) => "it requires `Self: Sized`".into(), + DynCompatibilityViolation::ExplicitlyDynIncompatible(_) => { + "it opted out of dyn-compatibility".into() + } DynCompatibilityViolation::SupertraitSelf(spans) => { if spans.iter().any(|sp| *sp != DUMMY_SP) { "it uses `Self` as a type parameter".into() @@ -861,6 +867,7 @@ impl DynCompatibilityViolation { pub fn solution(&self) -> DynCompatibilityViolationSolution { match self { DynCompatibilityViolation::SizedSelf(_) + | DynCompatibilityViolation::ExplicitlyDynIncompatible(_) | DynCompatibilityViolation::SupertraitSelf(_) | DynCompatibilityViolation::SupertraitNonLifetimeBinder(..) | DynCompatibilityViolation::SupertraitConst(_) => { @@ -894,6 +901,7 @@ impl DynCompatibilityViolation { match self { DynCompatibilityViolation::SupertraitSelf(spans) | DynCompatibilityViolation::SizedSelf(spans) + | DynCompatibilityViolation::ExplicitlyDynIncompatible(spans) | DynCompatibilityViolation::SupertraitNonLifetimeBinder(spans) | DynCompatibilityViolation::SupertraitConst(spans) => spans.clone(), DynCompatibilityViolation::AssocConst(_, span) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index fb515cf7f778..2f7a31a957bc 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -709,10 +709,6 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.trait_def(def_id).is_fundamental } - fn trait_may_be_implemented_via_object(self, trait_def_id: DefId) -> bool { - self.trait_def(trait_def_id).implement_via_object - } - fn trait_is_unsafe(self, trait_def_id: Self::DefId) -> bool { self.trait_def(trait_def_id).safety.is_unsafe() } diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index 2f6b38a619d2..0553561fac2c 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -7,6 +7,7 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::{self as hir, find_attr}; use rustc_macros::{Decodable, Encodable, HashStable}; +use rustc_span::Span; use tracing::debug; use crate::query::LocalCrate; @@ -69,10 +70,9 @@ pub struct TraitDef { /// must be implemented. pub must_implement_one_of: Option>, - /// Whether to add a builtin `dyn Trait: Trait` implementation. - /// This is enabled for all traits except ones marked with - /// `#[rustc_do_not_implement_via_object]`. - pub implement_via_object: bool, + /// Whether the trait should be considered dyn-incompatible, even if it otherwise + /// satisfies the requirements to be dyn-compatible. + pub force_dyn_incompatible: Option, /// Whether a trait is fully built-in, and any implementation is disallowed. /// This only applies to built-in traits, and is marked via diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index d27d80a086ad..63f246db8a5f 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -792,7 +792,9 @@ where candidates: &mut Vec>, ) { let cx = self.cx(); - if !cx.trait_may_be_implemented_via_object(goal.predicate.trait_def_id(cx)) { + if cx.is_sizedness_trait(goal.predicate.trait_def_id(cx)) { + // `dyn MetaSized` is valid, but should get its `MetaSized` impl from + // being `dyn` (SizedCandidate), not from the object candidate. return; } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 4266731cf0a2..f3cb05aed6d5 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -244,7 +244,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::SkipDuringMethodDispatch { .. } | AttributeKind::Coinductive(..) | AttributeKind::DenyExplicitImpl(..) - | AttributeKind::DoNotImplementViaObject(..) + | AttributeKind::DynIncompatibleTrait(..) | AttributeKind::SpecializationTrait(..) | AttributeKind::UnsafeSpecializationMarker(..) | AttributeKind::ParenSugar(..) diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index 14656a2e594a..f0a18ef08fc6 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -1395,7 +1395,7 @@ pub struct TraitDecl { pub skip_boxed_slice_during_method_dispatch: bool, pub specialization_kind: TraitSpecializationKind, pub must_implement_one_of: Option>, - pub implement_via_object: bool, + pub force_dyn_incompatible: Option, pub deny_explicit_impl: bool, } diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index ca8234280be8..34662c9ca023 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -597,7 +597,7 @@ impl<'tcx> Stable<'tcx> for ty::TraitDef { .must_implement_one_of .as_ref() .map(|idents| idents.iter().map(|ident| opaque(ident)).collect()), - implement_via_object: self.implement_via_object, + force_dyn_incompatible: self.force_dyn_incompatible.stable(tables, cx), deny_explicit_impl: self.deny_explicit_impl, } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e5949a979074..d6d6ebb49e10 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1950,7 +1950,6 @@ symbols! { rustc_diagnostic_macros, rustc_dirty, rustc_do_not_const_check, - rustc_do_not_implement_via_object, rustc_doc_primitive, rustc_driver, rustc_dummy, @@ -1959,6 +1958,7 @@ symbols! { rustc_dump_predicates, rustc_dump_user_args, rustc_dump_vtable, + rustc_dyn_incompatible_trait, rustc_effective_visibility, rustc_eii_foreign_item, rustc_evaluate_where_clauses, diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 6ab92531e4ef..13acdad8aad7 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -93,7 +93,10 @@ fn dyn_compatibility_violations_for_trait( // We don't want to include the requirement from `Sized` itself to be `Sized` in the list. let spans = get_sized_bounds(tcx, trait_def_id); violations.push(DynCompatibilityViolation::SizedSelf(spans)); + } else if let Some(span) = tcx.trait_def(trait_def_id).force_dyn_incompatible { + violations.push(DynCompatibilityViolation::ExplicitlyDynIncompatible([span].into())); } + let spans = predicates_reference_self(tcx, trait_def_id, false); if !spans.is_empty() { violations.push(DynCompatibilityViolation::SupertraitSelf(spans)); diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index e5c2adaa261d..5bbbad0b5bbf 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -799,10 +799,6 @@ fn assemble_candidates_from_object_ty<'cx, 'tcx>( let tcx = selcx.tcx(); - if !tcx.trait_def(obligation.predicate.trait_def_id(tcx)).implement_via_object { - return; - } - let self_ty = obligation.predicate.self_ty(); let object_ty = selcx.infcx.shallow_resolve(self_ty); let data = match object_ty.kind() { diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index d0833f030835..f5bf74a79919 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -904,7 +904,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { "assemble_candidates_from_object_ty", ); - if !self.tcx().trait_def(obligation.predicate.def_id()).implement_via_object { + if self.tcx().is_sizedness_trait(obligation.predicate.def_id()) { + // `dyn MetaSized` is valid, but should get its `MetaSized` impl from + // being `dyn` (SizedCandidate), not from the object candidate. return; } diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 03cf738c0598..0ab27a65c687 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -378,8 +378,6 @@ pub trait Interner: fn trait_is_fundamental(self, def_id: Self::TraitId) -> bool; - fn trait_may_be_implemented_via_object(self, trait_def_id: Self::TraitId) -> bool; - /// Returns `true` if this is an `unsafe trait`. fn trait_is_unsafe(self, trait_def_id: Self::TraitId) -> bool; diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index da7ba167ef74..57416455e9de 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -153,7 +153,7 @@ unsafe impl Send for &T {} #[fundamental] // for Default, for example, which requires that `[T]: !Default` be evaluatable #[rustc_specialization_trait] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] // `Sized` being coinductive, despite having supertraits, is okay as there are no user-written impls, // and we know that the supertraits are always implemented if the subtrait is just by looking at // the builtin impls. @@ -172,7 +172,6 @@ pub trait Sized: MetaSized { #[fundamental] #[rustc_specialization_trait] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] // `MetaSized` being coinductive, despite having supertraits, is okay for the same reasons as // `Sized` above. #[rustc_coinductive] @@ -190,7 +189,6 @@ pub trait MetaSized: PointeeSized { #[fundamental] #[rustc_specialization_trait] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] #[rustc_coinductive] pub trait PointeeSized { // Empty @@ -236,7 +234,7 @@ pub trait PointeeSized { #[unstable(feature = "unsize", issue = "18598")] #[lang = "unsize"] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] pub trait Unsize: PointeeSized { // Empty. } @@ -509,7 +507,7 @@ impl Copy for &T {} #[unstable(feature = "bikeshed_guaranteed_no_drop", issue = "none")] #[lang = "bikeshed_guaranteed_no_drop"] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] #[doc(hidden)] pub trait BikeshedGuaranteedNoDrop {} @@ -884,7 +882,7 @@ impl StructuralPartialEq for PhantomData {} )] #[lang = "discriminant_kind"] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] pub trait DiscriminantKind { /// The type of the discriminant, which must satisfy the trait /// bounds required by `mem::Discriminant`. @@ -1054,7 +1052,7 @@ marker_impls! { #[lang = "destruct"] #[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] pub const trait Destruct: PointeeSized {} /// A marker for tuple types. @@ -1065,7 +1063,7 @@ pub const trait Destruct: PointeeSized {} #[lang = "tuple_trait"] #[diagnostic::on_unimplemented(message = "`{Self}` is not a tuple")] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] pub trait Tuple {} /// A marker for types which can be used as types of `const` generic parameters. @@ -1123,7 +1121,7 @@ marker_impls! { )] #[lang = "fn_ptr_trait"] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] pub trait FnPtr: Copy + Clone { /// Returns the address of the function pointer. #[lang = "fn_ptr_addr"] diff --git a/library/core/src/mem/transmutability.rs b/library/core/src/mem/transmutability.rs index f36cb8cddb83..e26c1b8fa1e1 100644 --- a/library/core/src/mem/transmutability.rs +++ b/library/core/src/mem/transmutability.rs @@ -86,7 +86,7 @@ use crate::marker::ConstParamTy_; #[unstable_feature_bound(transmutability)] #[lang = "transmute_trait"] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] #[rustc_coinductive] pub unsafe trait TransmuteFrom where diff --git a/library/core/src/ptr/metadata.rs b/library/core/src/ptr/metadata.rs index 1a5864316833..1eeadf1217b5 100644 --- a/library/core/src/ptr/metadata.rs +++ b/library/core/src/ptr/metadata.rs @@ -55,7 +55,7 @@ use crate::ptr::NonNull; /// [`to_raw_parts`]: *const::to_raw_parts #[lang = "pointee_trait"] #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] pub trait Pointee: PointeeSized { /// The type for metadata in pointers and references to `Self`. #[lang = "metadata_type"] diff --git a/tests/ui/dyn-compatibility/metasized.rs b/tests/ui/dyn-compatibility/metasized.rs new file mode 100644 index 000000000000..ff233c5ca764 --- /dev/null +++ b/tests/ui/dyn-compatibility/metasized.rs @@ -0,0 +1,25 @@ +//@ run-pass +//! This test and `sized-*.rs` and `pointeesized.rs` test that dyn-compatibility correctly +//! handles sizedness traits, which are special in several parts of the compiler. +#![feature(sized_hierarchy)] +use std::marker::MetaSized; + +trait Foo: std::fmt::Debug + MetaSized {} + +impl Foo for T {} + +fn unsize_sized(x: Box) -> Box { + x +} + +fn unsize_subtrait(x: Box) -> Box { + x +} + +fn main() { + let _bx = unsize_sized(Box::new(vec![1, 2, 3])); + + let bx: Box = Box::new(vec![1, 2, 3]); + let _ = format!("{bx:?}"); + let _bx = unsize_subtrait(bx); +} diff --git a/tests/ui/dyn-compatibility/pointeesized.rs b/tests/ui/dyn-compatibility/pointeesized.rs new file mode 100644 index 000000000000..863abd704ead --- /dev/null +++ b/tests/ui/dyn-compatibility/pointeesized.rs @@ -0,0 +1,17 @@ +//@ run-pass +//! This test and `sized-*.rs` and `metasized.rs` test that dyn-compatibility correctly +//! handles sizedness traits, which are special in several parts of the compiler. +#![feature(sized_hierarchy)] +// PointeeSized is effectively removed before reaching the trait solver, +// so it's as though it wasn't even mentioned in the trait list. +use std::marker::PointeeSized; + +fn main() { + let dyn_ref: &(dyn PointeeSized + Send) = &42; + let dyn_ref: &dyn Send = dyn_ref; + let _dyn_ref: &(dyn PointeeSized + Send) = dyn_ref; + assert_eq!( + std::any::TypeId::of::(), + std::any::TypeId::of::(), + ); +} diff --git a/tests/ui/dyn-compatibility/sized-3.rs b/tests/ui/dyn-compatibility/sized-3.rs new file mode 100644 index 000000000000..84ee391445e7 --- /dev/null +++ b/tests/ui/dyn-compatibility/sized-3.rs @@ -0,0 +1,29 @@ +//! This test and `metasized.rs` and `pointeesized.rs` test that dyn-compatibility correctly +//! handles the different sizedness traits, which are special in several parts of the compiler. + +trait Foo: std::fmt::Debug + Sized {} + +impl Foo for T {} + +fn unsize_sized(x: Box) -> Box { + //~^ ERROR the trait `Sized` is not dyn compatible + x +} + +fn unsize_subtrait(x: Box) -> Box { + //~^ ERROR the trait `Foo` is not dyn compatible + //~| ERROR the trait `Sized` is not dyn compatible + x +} + +fn main() { + let _bx = unsize_sized(Box::new(vec![1, 2, 3])); + //~^ ERROR the trait `Sized` is not dyn compatible + + let bx: Box = Box::new(vec![1, 2, 3]); + //~^ ERROR the trait `Foo` is not dyn compatible + let _ = format!("{bx:?}"); + let _bx = unsize_subtrait(bx); + //~^ ERROR the trait `Foo` is not dyn compatible + //~| ERROR the trait `Sized` is not dyn compatible +} diff --git a/tests/ui/dyn-compatibility/sized-3.stderr b/tests/ui/dyn-compatibility/sized-3.stderr new file mode 100644 index 000000000000..88d3bb5810fe --- /dev/null +++ b/tests/ui/dyn-compatibility/sized-3.stderr @@ -0,0 +1,88 @@ +error[E0038]: the trait `Sized` is not dyn compatible + --> $DIR/sized-3.rs:8:47 + | +LL | fn unsize_sized(x: Box) -> Box { + | ^^^^^^^^^ `Sized` is not dyn compatible + | + = note: the trait is not dyn compatible because it requires `Self: Sized` + = note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + +error[E0038]: the trait `Foo` is not dyn compatible + --> $DIR/sized-3.rs:13:27 + | +LL | fn unsize_subtrait(x: Box) -> Box { + | ^^^^^^^ `Foo` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/sized-3.rs:4:30 + | +LL | trait Foo: std::fmt::Debug + Sized {} + | --- ^^^^^ ...because it requires `Self: Sized` + | | + | this trait is not dyn compatible... + +error[E0038]: the trait `Sized` is not dyn compatible + --> $DIR/sized-3.rs:13:44 + | +LL | fn unsize_subtrait(x: Box) -> Box { + | ^^^^^^^^^ `Sized` is not dyn compatible + | + = note: the trait is not dyn compatible because it requires `Self: Sized` + = note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + +error[E0038]: the trait `Sized` is not dyn compatible + --> $DIR/sized-3.rs:20:15 + | +LL | let _bx = unsize_sized(Box::new(vec![1, 2, 3])); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Sized` is not dyn compatible + | + = note: the trait is not dyn compatible because it requires `Self: Sized` + = note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + +error[E0038]: the trait `Foo` is not dyn compatible + --> $DIR/sized-3.rs:23:21 + | +LL | let bx: Box = Box::new(vec![1, 2, 3]); + | ^^^ `Foo` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/sized-3.rs:4:30 + | +LL | trait Foo: std::fmt::Debug + Sized {} + | --- ^^^^^ ...because it requires `Self: Sized` + | | + | this trait is not dyn compatible... + +error[E0038]: the trait `Foo` is not dyn compatible + --> $DIR/sized-3.rs:26:31 + | +LL | let _bx = unsize_subtrait(bx); + | ^^ `Foo` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/sized-3.rs:4:30 + | +LL | trait Foo: std::fmt::Debug + Sized {} + | --- ^^^^^ ...because it requires `Self: Sized` + | | + | this trait is not dyn compatible... + +error[E0038]: the trait `Sized` is not dyn compatible + --> $DIR/sized-3.rs:26:15 + | +LL | let _bx = unsize_subtrait(bx); + | ^^^^^^^^^^^^^^^^^^^ `Sized` is not dyn compatible + | + = note: the trait is not dyn compatible because it requires `Self: Sized` + = note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/traits/deny-builtin-object-impl.current.stderr b/tests/ui/traits/deny-builtin-object-impl.current.stderr index d6f4762d0996..928d16aa4329 100644 --- a/tests/ui/traits/deny-builtin-object-impl.current.stderr +++ b/tests/ui/traits/deny-builtin-object-impl.current.stderr @@ -4,41 +4,67 @@ error[E0322]: explicit impls for the `NotImplYesObject` trait are not permitted LL | impl NotImplYesObject for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `NotImplYesObject` not allowed -error[E0277]: the trait bound `dyn NotImplNotObject: NotImplNotObject` is not satisfied - --> $DIR/deny-builtin-object-impl.rs:37:32 +error[E0038]: the trait `YesImplNotObject2` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:23:28 + | +LL | impl YesImplNotObject2 for dyn YesImplNotObject2 {} + | ^^^^^^^^^^^^^^^^^^^^^ `YesImplNotObject2` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:17:1 + | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility +LL | trait YesImplNotObject2 {} + | ----------------- this trait is not dyn compatible... + +error[E0038]: the trait `NotImplNotObject` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:37:36 | LL | test_not_impl_not_object::(); - | ^^^^^^^^^^^^^^^^^^^^ the trait `NotImplNotObject` is not implemented for `dyn NotImplNotObject` + | ^^^^^^^^^^^^^^^^ `NotImplNotObject` is not dyn compatible | -help: this trait has no implementations, consider adding one - --> $DIR/deny-builtin-object-impl.rs:12:1 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:11:1 | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility LL | trait NotImplNotObject {} - | ^^^^^^^^^^^^^^^^^^^^^^ -note: required by a bound in `test_not_impl_not_object` - --> $DIR/deny-builtin-object-impl.rs:28:32 - | -LL | fn test_not_impl_not_object() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `test_not_impl_not_object` + | ---------------- this trait is not dyn compatible... -error[E0277]: the trait bound `dyn YesImplNotObject: YesImplNotObject` is not satisfied - --> $DIR/deny-builtin-object-impl.rs:40:32 +error[E0038]: the trait `YesImplNotObject` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:40:36 | LL | test_yes_impl_not_object::(); - | ^^^^^^^^^^^^^^^^^^^^ the trait `YesImplNotObject` is not implemented for `dyn YesImplNotObject` + | ^^^^^^^^^^^^^^^^ `YesImplNotObject` is not dyn compatible | -help: this trait has no implementations, consider adding one - --> $DIR/deny-builtin-object-impl.rs:15:1 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:14:1 | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility LL | trait YesImplNotObject {} - | ^^^^^^^^^^^^^^^^^^^^^^ -note: required by a bound in `test_yes_impl_not_object` - --> $DIR/deny-builtin-object-impl.rs:30:32 + | ---------------- this trait is not dyn compatible... + +error[E0038]: the trait `YesImplNotObject2` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:43:37 | -LL | fn test_yes_impl_not_object() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `test_yes_impl_not_object` +LL | test_yes_impl_not_object2::(); + | ^^^^^^^^^^^^^^^^^ `YesImplNotObject2` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:17:1 + | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility +LL | trait YesImplNotObject2 {} + | ----------------- this trait is not dyn compatible... -error: aborting due to 3 previous errors +error: aborting due to 5 previous errors -Some errors have detailed explanations: E0277, E0322. -For more information about an error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0038, E0322. +For more information about an error, try `rustc --explain E0038`. diff --git a/tests/ui/traits/deny-builtin-object-impl.next.stderr b/tests/ui/traits/deny-builtin-object-impl.next.stderr index d6f4762d0996..928d16aa4329 100644 --- a/tests/ui/traits/deny-builtin-object-impl.next.stderr +++ b/tests/ui/traits/deny-builtin-object-impl.next.stderr @@ -4,41 +4,67 @@ error[E0322]: explicit impls for the `NotImplYesObject` trait are not permitted LL | impl NotImplYesObject for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `NotImplYesObject` not allowed -error[E0277]: the trait bound `dyn NotImplNotObject: NotImplNotObject` is not satisfied - --> $DIR/deny-builtin-object-impl.rs:37:32 +error[E0038]: the trait `YesImplNotObject2` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:23:28 + | +LL | impl YesImplNotObject2 for dyn YesImplNotObject2 {} + | ^^^^^^^^^^^^^^^^^^^^^ `YesImplNotObject2` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:17:1 + | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility +LL | trait YesImplNotObject2 {} + | ----------------- this trait is not dyn compatible... + +error[E0038]: the trait `NotImplNotObject` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:37:36 | LL | test_not_impl_not_object::(); - | ^^^^^^^^^^^^^^^^^^^^ the trait `NotImplNotObject` is not implemented for `dyn NotImplNotObject` + | ^^^^^^^^^^^^^^^^ `NotImplNotObject` is not dyn compatible | -help: this trait has no implementations, consider adding one - --> $DIR/deny-builtin-object-impl.rs:12:1 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:11:1 | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility LL | trait NotImplNotObject {} - | ^^^^^^^^^^^^^^^^^^^^^^ -note: required by a bound in `test_not_impl_not_object` - --> $DIR/deny-builtin-object-impl.rs:28:32 - | -LL | fn test_not_impl_not_object() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `test_not_impl_not_object` + | ---------------- this trait is not dyn compatible... -error[E0277]: the trait bound `dyn YesImplNotObject: YesImplNotObject` is not satisfied - --> $DIR/deny-builtin-object-impl.rs:40:32 +error[E0038]: the trait `YesImplNotObject` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:40:36 | LL | test_yes_impl_not_object::(); - | ^^^^^^^^^^^^^^^^^^^^ the trait `YesImplNotObject` is not implemented for `dyn YesImplNotObject` + | ^^^^^^^^^^^^^^^^ `YesImplNotObject` is not dyn compatible | -help: this trait has no implementations, consider adding one - --> $DIR/deny-builtin-object-impl.rs:15:1 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:14:1 | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility LL | trait YesImplNotObject {} - | ^^^^^^^^^^^^^^^^^^^^^^ -note: required by a bound in `test_yes_impl_not_object` - --> $DIR/deny-builtin-object-impl.rs:30:32 + | ---------------- this trait is not dyn compatible... + +error[E0038]: the trait `YesImplNotObject2` is not dyn compatible + --> $DIR/deny-builtin-object-impl.rs:43:37 | -LL | fn test_yes_impl_not_object() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `test_yes_impl_not_object` +LL | test_yes_impl_not_object2::(); + | ^^^^^^^^^^^^^^^^^ `YesImplNotObject2` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/deny-builtin-object-impl.rs:17:1 + | +LL | #[rustc_dyn_incompatible_trait] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...because it opted out of dyn-compatibility +LL | trait YesImplNotObject2 {} + | ----------------- this trait is not dyn compatible... -error: aborting due to 3 previous errors +error: aborting due to 5 previous errors -Some errors have detailed explanations: E0277, E0322. -For more information about an error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0038, E0322. +For more information about an error, try `rustc --explain E0038`. diff --git a/tests/ui/traits/deny-builtin-object-impl.rs b/tests/ui/traits/deny-builtin-object-impl.rs index 9d02ab7bd469..5924b3fa2857 100644 --- a/tests/ui/traits/deny-builtin-object-impl.rs +++ b/tests/ui/traits/deny-builtin-object-impl.rs @@ -8,20 +8,20 @@ trait NotImplYesObject {} #[rustc_deny_explicit_impl] -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] trait NotImplNotObject {} -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] trait YesImplNotObject {} -#[rustc_do_not_implement_via_object] +#[rustc_dyn_incompatible_trait] trait YesImplNotObject2 {} impl NotImplYesObject for () {} //~^ ERROR explicit impls for the `NotImplYesObject` trait are not permitted -// If there is no automatic impl then we can add a manual impl: impl YesImplNotObject2 for dyn YesImplNotObject2 {} +//~^ ERROR the trait `YesImplNotObject2` is not dyn compatible fn test_not_impl_yes_object() {} @@ -35,10 +35,11 @@ fn main() { test_not_impl_yes_object::(); test_not_impl_not_object::(); - //~^ ERROR the trait bound `dyn NotImplNotObject: NotImplNotObject` is not satisfied + //~^ ERROR the trait `NotImplNotObject` is not dyn compatible test_yes_impl_not_object::(); - //~^ ERROR the trait bound `dyn YesImplNotObject: YesImplNotObject` is not satisfied + //~^ ERROR the trait `YesImplNotObject` is not dyn compatible test_yes_impl_not_object2::(); + //~^ ERROR the trait `YesImplNotObject2` is not dyn compatible } diff --git a/tests/ui/traits/ice-with-dyn-pointee-errors.rs b/tests/ui/traits/ice-with-dyn-pointee-errors.rs index 46cef2c8bc09..bb89f103e3f9 100644 --- a/tests/ui/traits/ice-with-dyn-pointee-errors.rs +++ b/tests/ui/traits/ice-with-dyn-pointee-errors.rs @@ -6,10 +6,12 @@ use core::ptr::Pointee; fn unknown_sized_object_ptr_in(_: &(impl Pointee + ?Sized)) {} fn raw_pointer_in(x: &dyn Pointee) { + //~^ ERROR the trait `Pointee` is not dyn compatible unknown_sized_object_ptr_in(x) - //~^ ERROR type mismatch resolving ` as Pointee>::Metadata == ()` + //~^ ERROR the trait `Pointee` is not dyn compatible } fn main() { raw_pointer_in(&42) + //~^ ERROR the trait `Pointee` is not dyn compatible } diff --git a/tests/ui/traits/ice-with-dyn-pointee-errors.stderr b/tests/ui/traits/ice-with-dyn-pointee-errors.stderr index 5299236026d7..9ff0445cda09 100644 --- a/tests/ui/traits/ice-with-dyn-pointee-errors.stderr +++ b/tests/ui/traits/ice-with-dyn-pointee-errors.stderr @@ -1,19 +1,39 @@ -error[E0271]: type mismatch resolving ` as Pointee>::Metadata == ()` - --> $DIR/ice-with-dyn-pointee-errors.rs:9:33 +error[E0038]: the trait `Pointee` is not dyn compatible + --> $DIR/ice-with-dyn-pointee-errors.rs:8:23 + | +LL | fn raw_pointer_in(x: &dyn Pointee) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `Pointee` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $SRC_DIR/core/src/ptr/metadata.rs:LL:COL + | + = note: the trait is not dyn compatible because it opted out of dyn-compatibility + +error[E0038]: the trait `Pointee` is not dyn compatible + --> $DIR/ice-with-dyn-pointee-errors.rs:10:5 | LL | unknown_sized_object_ptr_in(x) - | --------------------------- ^ expected `()`, found `DynMetadata>` - | | - | required by a bound introduced by this call + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Pointee` is not dyn compatible | - = note: expected unit type `()` - found struct `DynMetadata>` -note: required by a bound in `unknown_sized_object_ptr_in` - --> $DIR/ice-with-dyn-pointee-errors.rs:6:50 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $SRC_DIR/core/src/ptr/metadata.rs:LL:COL | -LL | fn unknown_sized_object_ptr_in(_: &(impl Pointee + ?Sized)) {} - | ^^^^^^^^^^^^^ required by this bound in `unknown_sized_object_ptr_in` + = note: the trait is not dyn compatible because it opted out of dyn-compatibility -error: aborting due to 1 previous error +error[E0038]: the trait `Pointee` is not dyn compatible + --> $DIR/ice-with-dyn-pointee-errors.rs:15:20 + | +LL | raw_pointer_in(&42) + | ^^^ `Pointee` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $SRC_DIR/core/src/ptr/metadata.rs:LL:COL + | + = note: the trait is not dyn compatible because it opted out of dyn-compatibility -For more information about this error, try `rustc --explain E0271`. +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/traits/ice-with-dyn-pointee.rs b/tests/ui/traits/ice-with-dyn-pointee.rs deleted file mode 100644 index 45361cc44600..000000000000 --- a/tests/ui/traits/ice-with-dyn-pointee.rs +++ /dev/null @@ -1,11 +0,0 @@ -//@ run-pass -#![feature(ptr_metadata)] -// Address issue #112737 -- ICE with dyn Pointee -extern crate core; -use core::ptr::Pointee; - -fn raw_pointer_in(_: &dyn Pointee) {} - -fn main() { - raw_pointer_in(&42) -} diff --git a/tests/ui/unsized/issue-71659.current.stderr b/tests/ui/unsized/issue-71659.current.stderr index f7de668ba3a5..22e43e07dbda 100644 --- a/tests/ui/unsized/issue-71659.current.stderr +++ b/tests/ui/unsized/issue-71659.current.stderr @@ -1,18 +1,22 @@ -error[E0277]: the trait bound `dyn Foo: CastTo<[i32]>` is not satisfied - --> $DIR/issue-71659.rs:34:15 +error[E0038]: the trait `Foo` is not dyn compatible + --> $DIR/issue-71659.rs:33:17 | -LL | let x = x.cast::<[i32]>(); - | ^^^^ the trait `CastTo<[i32]>` is not implemented for `dyn Foo` +LL | let x: &dyn Foo = &[]; + | ^^^ `Foo` is not dyn compatible | -note: required by a bound in `Cast::cast` - --> $DIR/issue-71659.rs:23:15 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $SRC_DIR/core/src/marker.rs:LL:COL | -LL | fn cast(&self) -> &T - | ---- required by a bound in this associated function -LL | where -LL | Self: CastTo, - | ^^^^^^^^^ required by this bound in `Cast::cast` + = note: ...because it opted out of dyn-compatibility + | + ::: $DIR/issue-71659.rs:29:11 + | +LL | pub trait Foo: CastTo<[i32]> {} + | --- this trait is not dyn compatible... + = help: only type `[i32; 0]` implements `Foo` within this crate; consider using it directly instead. + = note: `Foo` may be implemented in other crates; if you want to support your users passing their own types here, you can't refer to a specific type error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/unsized/issue-71659.next.stderr b/tests/ui/unsized/issue-71659.next.stderr index f7de668ba3a5..22e43e07dbda 100644 --- a/tests/ui/unsized/issue-71659.next.stderr +++ b/tests/ui/unsized/issue-71659.next.stderr @@ -1,18 +1,22 @@ -error[E0277]: the trait bound `dyn Foo: CastTo<[i32]>` is not satisfied - --> $DIR/issue-71659.rs:34:15 +error[E0038]: the trait `Foo` is not dyn compatible + --> $DIR/issue-71659.rs:33:17 | -LL | let x = x.cast::<[i32]>(); - | ^^^^ the trait `CastTo<[i32]>` is not implemented for `dyn Foo` +LL | let x: &dyn Foo = &[]; + | ^^^ `Foo` is not dyn compatible | -note: required by a bound in `Cast::cast` - --> $DIR/issue-71659.rs:23:15 +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $SRC_DIR/core/src/marker.rs:LL:COL | -LL | fn cast(&self) -> &T - | ---- required by a bound in this associated function -LL | where -LL | Self: CastTo, - | ^^^^^^^^^ required by this bound in `Cast::cast` + = note: ...because it opted out of dyn-compatibility + | + ::: $DIR/issue-71659.rs:29:11 + | +LL | pub trait Foo: CastTo<[i32]> {} + | --- this trait is not dyn compatible... + = help: only type `[i32; 0]` implements `Foo` within this crate; consider using it directly instead. + = note: `Foo` may be implemented in other crates; if you want to support your users passing their own types here, you can't refer to a specific type error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/unsized/issue-71659.rs b/tests/ui/unsized/issue-71659.rs index c463ed125bb6..9e1387fa8443 100644 --- a/tests/ui/unsized/issue-71659.rs +++ b/tests/ui/unsized/issue-71659.rs @@ -31,6 +31,6 @@ impl Foo for [i32; 0] {} fn main() { let x: &dyn Foo = &[]; + //~^ ERROR: the trait `Foo` is not dyn compatible let x = x.cast::<[i32]>(); - //~^ ERROR: the trait bound `dyn Foo: CastTo<[i32]>` is not satisfied } From 005fcea374fe71f50e96c6b5d04928e6629227c0 Mon Sep 17 00:00:00 2001 From: Oscar Bray Date: Tue, 20 Jan 2026 19:22:25 +0000 Subject: [PATCH 135/273] Port variance attrs to attr parser. --- .../src/attributes/test_attrs.rs | 22 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 6 ++++- .../rustc_hir/src/attrs/data_structures.rs | 6 +++++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 ++ .../rustc_hir_analysis/src/variance/dump.rs | 7 +++--- compiler/rustc_passes/src/check_attr.rs | 4 ++-- .../feature-gate-rustc-attrs-1.rs | 10 +++++---- .../feature-gate-rustc-attrs-1.stderr | 20 ++++++++--------- 8 files changed, 57 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs index 7f25641b948e..ec7cdd3624dc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/test_attrs.rs @@ -91,3 +91,25 @@ impl SingleAttributeParser for ShouldPanicParser { }) } } + +pub(crate) struct RustcVarianceParser; + +impl NoArgsAttributeParser for RustcVarianceParser { + const PATH: &[Symbol] = &[sym::rustc_variance]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + Allow(Target::Struct), + Allow(Target::Enum), + Allow(Target::Union), + ]); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcVariance; +} + +pub(crate) struct RustcVarianceOfOpaquesParser; + +impl NoArgsAttributeParser for RustcVarianceOfOpaquesParser { + const PATH: &[Symbol] = &[sym::rustc_variance_of_opaques]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcVarianceOfOpaques; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 6aae2b90a504..26c1fc91aecf 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -85,7 +85,9 @@ use crate::attributes::semantics::MayDangleParser; use crate::attributes::stability::{ BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser, }; -use crate::attributes::test_attrs::{IgnoreParser, ShouldPanicParser}; +use crate::attributes::test_attrs::{ + IgnoreParser, RustcVarianceOfOpaquesParser, RustcVarianceParser, ShouldPanicParser, +}; use crate::attributes::traits::{ AllowIncoherentImplParser, CoinductiveParser, DenyExplicitImplParser, DoNotImplementViaObjectParser, FundamentalParser, MarkerParser, ParenSugarParser, @@ -300,6 +302,8 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 8d2baae703b8..fe48e6f4fafd 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1007,6 +1007,12 @@ pub enum AttributeKind { /// Represents `#[rustc_simd_monomorphize_lane_limit = "N"]`. RustcSimdMonomorphizeLaneLimit(Limit), + /// Represents `#[rustc_variance]` + RustcVariance, + + /// Represents `#[rustc_variance_of_opaques]` + RustcVarianceOfOpaques, + /// Represents `#[sanitize]` /// /// the on set and off set are distjoint since there's a third option: unset. diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index fd764ad458b1..5b593778b75a 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -129,6 +129,8 @@ impl AttributeKind { RustcScalableVector { .. } => Yes, RustcShouldNotBeCalledOnConstItems(..) => Yes, RustcSimdMonomorphizeLaneLimit(..) => Yes, // Affects layout computation, which needs to work cross-crate + RustcVariance => No, + RustcVarianceOfOpaques => No, Sanitize { .. } => No, ShouldPanic { .. } => No, SkipDuringMethodDispatch { .. } => No, diff --git a/compiler/rustc_hir_analysis/src/variance/dump.rs b/compiler/rustc_hir_analysis/src/variance/dump.rs index 31f0adf720d7..e625d9dfe3b5 100644 --- a/compiler/rustc_hir_analysis/src/variance/dump.rs +++ b/compiler/rustc_hir_analysis/src/variance/dump.rs @@ -1,8 +1,9 @@ use std::fmt::Write; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; +use rustc_hir::find_attr; use rustc_middle::ty::{GenericArgs, TyCtxt}; -use rustc_span::sym; fn format_variances(tcx: TyCtxt<'_>, def_id: LocalDefId) -> String { let variances = tcx.variances_of(def_id); @@ -25,7 +26,7 @@ fn format_variances(tcx: TyCtxt<'_>, def_id: LocalDefId) -> String { pub(crate) fn variances(tcx: TyCtxt<'_>) { let crate_items = tcx.hir_crate_items(()); - if tcx.has_attr(CRATE_DEF_ID, sym::rustc_variance_of_opaques) { + if find_attr!(tcx.get_all_attrs(CRATE_DEF_ID), AttributeKind::RustcVarianceOfOpaques) { for id in crate_items.opaques() { tcx.dcx().emit_err(crate::errors::VariancesOf { span: tcx.def_span(id), @@ -35,7 +36,7 @@ pub(crate) fn variances(tcx: TyCtxt<'_>) { } for id in crate_items.free_items() { - if !tcx.has_attr(id.owner_id, sym::rustc_variance) { + if !find_attr!(tcx.get_all_attrs(id.owner_id), AttributeKind::RustcVariance) { continue; } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 4266731cf0a2..d89a71c1deb6 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -274,6 +274,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcScalableVector { .. } | AttributeKind::RustcSimdMonomorphizeLaneLimit(..) | AttributeKind::RustcShouldNotBeCalledOnConstItems(..) + | AttributeKind::RustcVariance + | AttributeKind::RustcVarianceOfOpaques | AttributeKind::ExportStable | AttributeKind::FfiConst(..) | AttributeKind::UnstableFeatureBound(..) @@ -378,8 +380,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::rustc_capture_analysis | sym::rustc_regions | sym::rustc_strict_coherence - | sym::rustc_variance - | sym::rustc_variance_of_opaques | sym::rustc_hidden_type_of_opaques | sym::rustc_mir | sym::rustc_effective_visibility diff --git a/tests/ui/feature-gates/feature-gate-rustc-attrs-1.rs b/tests/ui/feature-gates/feature-gate-rustc-attrs-1.rs index 17556723622e..f3d5bcbd8d78 100644 --- a/tests/ui/feature-gates/feature-gate-rustc-attrs-1.rs +++ b/tests/ui/feature-gates/feature-gate-rustc-attrs-1.rs @@ -1,12 +1,14 @@ // Test that `#[rustc_*]` attributes are gated by `rustc_attrs` feature gate. -#[rustc_variance] -//~^ ERROR use of an internal attribute [E0658] -//~| NOTE the `#[rustc_variance]` attribute is an internal implementation detail that will never be stable -//~| NOTE the `#[rustc_variance]` attribute is used for rustc unit tests #[rustc_nonnull_optimization_guaranteed] //~^ ERROR use of an internal attribute [E0658] //~| NOTE the `#[rustc_nonnull_optimization_guaranteed]` attribute is an internal implementation detail that will never be stable //~| NOTE the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to document guaranteed niche optimizations in the standard library //~| NOTE the compiler does not even check whether the type indeed is being non-null-optimized; it is your responsibility to ensure that the attribute is only used on types that are optimized fn main() {} + +#[rustc_variance] +//~^ ERROR use of an internal attribute [E0658] +//~| NOTE the `#[rustc_variance]` attribute is an internal implementation detail that will never be stable +//~| NOTE the `#[rustc_variance]` attribute is used for rustc unit tests +enum E {} diff --git a/tests/ui/feature-gates/feature-gate-rustc-attrs-1.stderr b/tests/ui/feature-gates/feature-gate-rustc-attrs-1.stderr index 159d383e4089..6588229b7d56 100644 --- a/tests/ui/feature-gates/feature-gate-rustc-attrs-1.stderr +++ b/tests/ui/feature-gates/feature-gate-rustc-attrs-1.stderr @@ -1,16 +1,6 @@ error[E0658]: use of an internal attribute --> $DIR/feature-gate-rustc-attrs-1.rs:3:1 | -LL | #[rustc_variance] - | ^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable - = note: the `#[rustc_variance]` attribute is an internal implementation detail that will never be stable - = note: the `#[rustc_variance]` attribute is used for rustc unit tests - -error[E0658]: use of an internal attribute - --> $DIR/feature-gate-rustc-attrs-1.rs:7:1 - | LL | #[rustc_nonnull_optimization_guaranteed] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | @@ -19,6 +9,16 @@ LL | #[rustc_nonnull_optimization_guaranteed] = note: the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to document guaranteed niche optimizations in the standard library = note: the compiler does not even check whether the type indeed is being non-null-optimized; it is your responsibility to ensure that the attribute is only used on types that are optimized +error[E0658]: use of an internal attribute + --> $DIR/feature-gate-rustc-attrs-1.rs:10:1 + | +LL | #[rustc_variance] + | ^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable + = note: the `#[rustc_variance]` attribute is an internal implementation detail that will never be stable + = note: the `#[rustc_variance]` attribute is used for rustc unit tests + error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0658`. From 39296ff8f820ad9a5c1656428c8aae203c0934d6 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Tue, 20 Jan 2026 20:11:35 +0000 Subject: [PATCH 136/273] s390x: Support aligned stack datalayout LLVM 23 will mark the stack as aligned for more efficient code: https://github.com/llvm/llvm-project/pull/176041 --- compiler/rustc_codegen_llvm/src/context.rs | 6 ++++++ .../src/spec/targets/s390x_unknown_linux_gnu.rs | 2 +- .../src/spec/targets/s390x_unknown_linux_musl.rs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 4b2544b7efdf..04525b142e9b 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -216,6 +216,12 @@ pub(crate) unsafe fn create_module<'ll>( target_data_layout = target_data_layout.replace("-f64:32:64", ""); } } + if llvm_version < (23, 0, 0) { + if sess.target.arch == Arch::S390x { + // LLVM 23 updated the s390x layout to specify the stack alignment: https://github.com/llvm/llvm-project/pull/176041 + target_data_layout = target_data_layout.replace("-S64", ""); + } + } // Ensure the data-layout values hardcoded remain the defaults. { diff --git a/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_gnu.rs index a64e867b9a1c..8859e0d650a2 100644 --- a/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_gnu.rs @@ -22,7 +22,7 @@ pub(crate) fn target() -> Target { std: Some(true), }, pointer_width: 64, - data_layout: "E-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64".into(), + data_layout: "E-S64-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64".into(), arch: Arch::S390x, options: base, } diff --git a/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_musl.rs index 69c25d72432c..21e705ebbec5 100644 --- a/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/targets/s390x_unknown_linux_musl.rs @@ -23,7 +23,7 @@ pub(crate) fn target() -> Target { std: Some(true), }, pointer_width: 64, - data_layout: "E-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64".into(), + data_layout: "E-S64-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64".into(), arch: Arch::S390x, options: base, } From 76438f032a1dd57e624f326f126b639f1c2a7e68 Mon Sep 17 00:00:00 2001 From: Jamie Hill-Daniel Date: Thu, 14 Aug 2025 13:32:31 +0000 Subject: [PATCH 137/273] Add codegen test for issue 138497 --- ...sue-138497-nonzero-remove-trailing-zeroes.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/codegen-llvm/issues/issue-138497-nonzero-remove-trailing-zeroes.rs diff --git a/tests/codegen-llvm/issues/issue-138497-nonzero-remove-trailing-zeroes.rs b/tests/codegen-llvm/issues/issue-138497-nonzero-remove-trailing-zeroes.rs new file mode 100644 index 000000000000..77cdbaf2bfe5 --- /dev/null +++ b/tests/codegen-llvm/issues/issue-138497-nonzero-remove-trailing-zeroes.rs @@ -0,0 +1,17 @@ +//! This test checks that removing trailing zeroes from a `NonZero`, +//! then creating a new `NonZero` from the result does not panic. + +//@ min-llvm-version: 21 +//@ compile-flags: -O -Zmerge-functions=disabled +#![crate_type = "lib"] + +use std::num::NonZero; + +// CHECK-LABEL: @remove_trailing_zeros +#[no_mangle] +pub fn remove_trailing_zeros(x: NonZero) -> NonZero { + // CHECK: %[[TRAILING:[a-z0-9_-]+]] = {{.*}} call {{.*}} i8 @llvm.cttz.i8(i8 %x, i1 true) + // CHECK-NEXT: %[[RET:[a-z0-9_-]+]] = lshr exact i8 %x, %[[TRAILING]] + // CHECK-NEXT: ret i8 %[[RET]] + NonZero::new(x.get() >> x.trailing_zeros()).unwrap() +} From cf3738a49e22bca0352f2fb30de0810077ca6774 Mon Sep 17 00:00:00 2001 From: rami3l Date: Tue, 20 Jan 2026 16:57:00 +0100 Subject: [PATCH 138/273] fix(build-manifest)!: limit `rustc-docs` to current host target in the manifest --- src/tools/build-manifest/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 4cec1b1f164b..37e8a6e8f1e9 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -301,11 +301,12 @@ impl Builder { | PkgType::LlvmTools | PkgType::RustAnalysis | PkgType::JsonDocs + | PkgType::RustcDocs | PkgType::RustcCodegenCranelift | PkgType::LlvmBitcodeLinker => { extensions.push(host_component(pkg)); } - PkgType::RustcDev | PkgType::RustcDocs => { + PkgType::RustcDev => { extensions.extend(HOSTS.iter().map(|target| Component::from_pkg(pkg, target))); } PkgType::RustSrc => { From 17c5b7a4b519ebc2c5ddeb3aa36ac7bec5ca9724 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 20 Jan 2026 20:28:35 +1100 Subject: [PATCH 139/273] Replace `make_dep_kind_name_array!` with a slice constant --- compiler/rustc_interface/src/passes.rs | 2 +- .../rustc_middle/src/dep_graph/dep_node.rs | 23 ++++++++----------- compiler/rustc_middle/src/dep_graph/mod.rs | 6 +++-- compiler/rustc_query_impl/src/plumbing.rs | 4 ---- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 12a6a616d64f..1684b7535e68 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -940,7 +940,7 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( let outputs = util::build_output_filenames(&pre_configured_attrs, sess); - let dep_type = DepsType { dep_names: rustc_query_impl::dep_kind_names() }; + let dep_type = DepsType { dep_names: rustc_middle::dep_graph::DEP_KIND_NAMES }; let dep_graph = setup_dep_graph(sess, crate_name, stable_crate_id, &dep_type); let cstore = diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 3ee1db67911f..a815a03c6458 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -24,15 +24,6 @@ macro_rules! define_dep_nodes { ($mod:ident) => {[ $($mod::$variant()),* ]}; } - #[macro_export] - macro_rules! make_dep_kind_name_array { - ($mod:ident) => { - vec! { - $(*$mod::$variant().name),* - } - }; - } - /// This enum serves as an index into arrays built by `make_dep_kind_array`. // This enum has more than u8::MAX variants so we need some kind of multi-byte // encoding. The derived Encodable/Decodable uses leb128 encoding which is @@ -68,20 +59,24 @@ macro_rules! define_dep_nodes { deps.len() as u16 }; + /// List containing the name of each dep kind as a static string, + /// indexable by `DepKind`. + pub const DEP_KIND_NAMES: &[&str] = &[ + $( self::label_strs::$variant, )* + ]; + pub(super) fn dep_kind_from_label_string(label: &str) -> Result { match label { - $(stringify!($variant) => Ok(dep_kinds::$variant),)* + $( self::label_strs::$variant => Ok(self::dep_kinds::$variant), )* _ => Err(()), } } /// Contains variant => str representations for constructing /// DepNode groups for tests. - #[allow(dead_code, non_upper_case_globals)] + #[expect(non_upper_case_globals)] pub mod label_strs { - $( - pub const $variant: &str = stringify!($variant); - )* + $( pub const $variant: &str = stringify!($variant); )* } }; } diff --git a/compiler/rustc_middle/src/dep_graph/mod.rs b/compiler/rustc_middle/src/dep_graph/mod.rs index b24ea4acc6b7..2fef7b5a9781 100644 --- a/compiler/rustc_middle/src/dep_graph/mod.rs +++ b/compiler/rustc_middle/src/dep_graph/mod.rs @@ -8,7 +8,9 @@ use crate::ty::{self, TyCtxt}; #[macro_use] mod dep_node; -pub use dep_node::{DepKind, DepNode, DepNodeExt, dep_kind_from_label, dep_kinds, label_strs}; +pub use dep_node::{ + DEP_KIND_NAMES, DepKind, DepNode, DepNodeExt, dep_kind_from_label, dep_kinds, label_strs, +}; pub(crate) use dep_node::{make_compile_codegen_unit, make_compile_mono_item, make_metadata}; pub use rustc_query_system::dep_graph::debug::{DepNodeFilter, EdgeFilter}; pub use rustc_query_system::dep_graph::{ @@ -22,7 +24,7 @@ pub type DepKindStruct<'tcx> = rustc_query_system::dep_graph::DepKindStruct, + pub dep_names: &'static [&'static str], } impl Deps for DepsType { diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 67ab1114af62..d6d1dc781f3e 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -921,9 +921,5 @@ macro_rules! define_queries { pub fn query_callbacks<'tcx>(arena: &'tcx Arena<'tcx>) -> &'tcx [DepKindStruct<'tcx>] { arena.alloc_from_iter(rustc_middle::make_dep_kind_array!(query_callbacks)) } - - pub fn dep_kind_names() -> Vec<&'static str> { - rustc_middle::make_dep_kind_name_array!(query_callbacks) - } } } From 154255698ac2686a24b8604fff913cd8d4db9daf Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 20 Jan 2026 20:41:07 +1100 Subject: [PATCH 140/273] Make `Deps::name` lookup a non-self associated function The dep names needed here are statically available from `rustc_middle`. --- compiler/rustc_incremental/src/persist/load.rs | 10 +++------- compiler/rustc_interface/src/passes.rs | 4 +--- compiler/rustc_middle/src/dep_graph/dep_node.rs | 2 +- compiler/rustc_middle/src/dep_graph/mod.rs | 12 ++++-------- compiler/rustc_query_system/src/dep_graph/mod.rs | 2 +- .../rustc_query_system/src/dep_graph/serialized.rs | 6 +++--- 6 files changed, 13 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs index 1b2a283a1a0d..e93cba2c8d0f 100644 --- a/compiler/rustc_incremental/src/persist/load.rs +++ b/compiler/rustc_incremental/src/persist/load.rs @@ -91,10 +91,7 @@ fn delete_dirty_work_product(sess: &Session, swp: SerializedWorkProduct) { work_product::delete_workproduct_files(sess, &swp.work_product); } -fn load_dep_graph( - sess: &Session, - deps: &DepsType, -) -> LoadResult<(Arc, WorkProductMap)> { +fn load_dep_graph(sess: &Session) -> LoadResult<(Arc, WorkProductMap)> { let prof = sess.prof.clone(); if sess.opts.incremental.is_none() { @@ -174,7 +171,7 @@ fn load_dep_graph( return LoadResult::DataOutOfDate; } - let dep_graph = SerializedDepGraph::decode::(&mut decoder, deps); + let dep_graph = SerializedDepGraph::decode::(&mut decoder); LoadResult::Ok { data: (dep_graph, prev_work_products) } } @@ -212,12 +209,11 @@ pub fn setup_dep_graph( sess: &Session, crate_name: Symbol, stable_crate_id: StableCrateId, - deps: &DepsType, ) -> DepGraph { // `load_dep_graph` can only be called after `prepare_session_directory`. prepare_session_directory(sess, crate_name, stable_crate_id); - let res = sess.opts.build_dep_graph().then(|| load_dep_graph(sess, deps)); + let res = sess.opts.build_dep_graph().then(|| load_dep_graph(sess)); if sess.opts.incremental.is_some() { sess.time("incr_comp_garbage_collect_session_directories", || { diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 1684b7535e68..a7e94e1c0155 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -26,7 +26,6 @@ use rustc_lint::{BufferedEarlyLint, EarlyCheckNode, LintStore, unerased_lint_sto use rustc_metadata::EncodedMetadata; use rustc_metadata::creader::CStore; use rustc_middle::arena::Arena; -use rustc_middle::dep_graph::DepsType; use rustc_middle::ty::{self, CurrentGcx, GlobalCtxt, RegisteredTools, TyCtxt}; use rustc_middle::util::Providers; use rustc_parse::lexer::StripTokens; @@ -940,8 +939,7 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( let outputs = util::build_output_filenames(&pre_configured_attrs, sess); - let dep_type = DepsType { dep_names: rustc_middle::dep_graph::DEP_KIND_NAMES }; - let dep_graph = setup_dep_graph(sess, crate_name, stable_crate_id, &dep_type); + let dep_graph = setup_dep_graph(sess, crate_name, stable_crate_id); let cstore = FreezeLock::new(Box::new(CStore::new(compiler.codegen_backend.metadata_loader())) as _); diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index a815a03c6458..f5ed0570667c 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -61,7 +61,7 @@ macro_rules! define_dep_nodes { /// List containing the name of each dep kind as a static string, /// indexable by `DepKind`. - pub const DEP_KIND_NAMES: &[&str] = &[ + pub(crate) const DEP_KIND_NAMES: &[&str] = &[ $( self::label_strs::$variant, )* ]; diff --git a/compiler/rustc_middle/src/dep_graph/mod.rs b/compiler/rustc_middle/src/dep_graph/mod.rs index 2fef7b5a9781..c72f75b43dab 100644 --- a/compiler/rustc_middle/src/dep_graph/mod.rs +++ b/compiler/rustc_middle/src/dep_graph/mod.rs @@ -8,9 +8,7 @@ use crate::ty::{self, TyCtxt}; #[macro_use] mod dep_node; -pub use dep_node::{ - DEP_KIND_NAMES, DepKind, DepNode, DepNodeExt, dep_kind_from_label, dep_kinds, label_strs, -}; +pub use dep_node::{DepKind, DepNode, DepNodeExt, dep_kind_from_label, dep_kinds, label_strs}; pub(crate) use dep_node::{make_compile_codegen_unit, make_compile_mono_item, make_metadata}; pub use rustc_query_system::dep_graph::debug::{DepNodeFilter, EdgeFilter}; pub use rustc_query_system::dep_graph::{ @@ -23,9 +21,7 @@ pub type DepGraph = rustc_query_system::dep_graph::DepGraph; pub type DepKindStruct<'tcx> = rustc_query_system::dep_graph::DepKindStruct>; #[derive(Clone)] -pub struct DepsType { - pub dep_names: &'static [&'static str], -} +pub struct DepsType; impl Deps for DepsType { fn with_deps(task_deps: TaskDepsRef<'_>, op: OP) -> R @@ -49,8 +45,8 @@ impl Deps for DepsType { }) } - fn name(&self, dep_kind: DepKind) -> &'static str { - self.dep_names[dep_kind.as_usize()] + fn name(dep_kind: DepKind) -> &'static str { + dep_node::DEP_KIND_NAMES[dep_kind.as_usize()] } const DEP_KIND_NULL: DepKind = dep_kinds::Null; diff --git a/compiler/rustc_query_system/src/dep_graph/mod.rs b/compiler/rustc_query_system/src/dep_graph/mod.rs index d648415c9fc6..8b9e4fe1bf29 100644 --- a/compiler/rustc_query_system/src/dep_graph/mod.rs +++ b/compiler/rustc_query_system/src/dep_graph/mod.rs @@ -103,7 +103,7 @@ pub trait Deps: DynSync { where OP: for<'a> FnOnce(TaskDepsRef<'a>); - fn name(&self, dep_kind: DepKind) -> &'static str; + fn name(dep_kind: DepKind) -> &'static str; /// We use this for most things when incr. comp. is turned off. const DEP_KIND_NULL: DepKind; diff --git a/compiler/rustc_query_system/src/dep_graph/serialized.rs b/compiler/rustc_query_system/src/dep_graph/serialized.rs index 0012bf79a1f5..403394674a02 100644 --- a/compiler/rustc_query_system/src/dep_graph/serialized.rs +++ b/compiler/rustc_query_system/src/dep_graph/serialized.rs @@ -191,8 +191,8 @@ fn mask(bits: usize) -> usize { } impl SerializedDepGraph { - #[instrument(level = "debug", skip(d, deps))] - pub fn decode(d: &mut MemDecoder<'_>, deps: &D) -> Arc { + #[instrument(level = "debug", skip(d))] + pub fn decode(d: &mut MemDecoder<'_>) -> Arc { // The last 16 bytes are the node count and edge count. debug!("position: {:?}", d.position()); @@ -280,7 +280,7 @@ impl SerializedDepGraph { if index[node.kind.as_usize()].insert(node.hash, idx).is_some() { // Empty nodes and side effect nodes can have duplicates if node.kind != D::DEP_KIND_NULL && node.kind != D::DEP_KIND_SIDE_EFFECT { - let name = deps.name(node.kind); + let name = D::name(node.kind); panic!( "Error: A dep graph node ({name}) does not have an unique index. \ Running a clean build on a nightly compiler with `-Z incremental-verify-ich` \ From 395ca3be21385c7c7cdab029798f9d5e66fd567f Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 21 Jan 2026 11:57:21 +1100 Subject: [PATCH 141/273] Remove `#[derive(Clone)]` from `DepsType` --- compiler/rustc_middle/src/dep_graph/mod.rs | 1 - compiler/rustc_query_system/src/dep_graph/graph.rs | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_middle/src/dep_graph/mod.rs b/compiler/rustc_middle/src/dep_graph/mod.rs index c72f75b43dab..049e868879e9 100644 --- a/compiler/rustc_middle/src/dep_graph/mod.rs +++ b/compiler/rustc_middle/src/dep_graph/mod.rs @@ -20,7 +20,6 @@ pub type DepGraph = rustc_query_system::dep_graph::DepGraph; pub type DepKindStruct<'tcx> = rustc_query_system::dep_graph::DepKindStruct>; -#[derive(Clone)] pub struct DepsType; impl Deps for DepsType { diff --git a/compiler/rustc_query_system/src/dep_graph/graph.rs b/compiler/rustc_query_system/src/dep_graph/graph.rs index f32f3f78c04b..f0cc9636b75c 100644 --- a/compiler/rustc_query_system/src/dep_graph/graph.rs +++ b/compiler/rustc_query_system/src/dep_graph/graph.rs @@ -28,7 +28,6 @@ use crate::dep_graph::edges::EdgesVec; use crate::ich::StableHashingContext; use crate::query::{QueryContext, QuerySideEffect}; -#[derive(Clone)] pub struct DepGraph { data: Option>>, @@ -39,6 +38,17 @@ pub struct DepGraph { virtual_dep_node_index: Arc, } +/// Manual clone impl that does not require `D: Clone`. +impl Clone for DepGraph { + fn clone(&self) -> Self { + let Self { data, virtual_dep_node_index } = self; + Self { + data: Option::>::clone(data), + virtual_dep_node_index: Arc::clone(virtual_dep_node_index), + } + } +} + rustc_index::newtype_index! { pub struct DepNodeIndex {} } From 5e960ef4187f0cc85a3ad854c3fa1b4a145d03f1 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Wed, 21 Jan 2026 09:58:33 +0800 Subject: [PATCH 142/273] triagebot: label `src/tools/build-manifest` with `T-bootstrap` --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index a25c2a0a388c..a5cc93809b72 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -499,6 +499,7 @@ trigger_files = [ "bootstrap.example.toml", "src/bootstrap", "src/build_helper", + "src/tools/build-manifest", "src/tools/rust-installer", "src/tools/x", "src/stage0", From 1973d65ade7b98d1fab453691d9ca1d51e094991 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Wed, 21 Jan 2026 09:59:34 +0800 Subject: [PATCH 143/273] misc: roll bootstrap reviewers for `src/tools/build-manifest` --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index a5cc93809b72..1572c4c8d045 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1657,6 +1657,7 @@ dep-bumps = [ "/tests/rustdoc-json" = ["@aDotInTheVoid"] "/tests/rustdoc-ui" = ["rustdoc"] "/tests/ui" = ["compiler"] +"/src/tools/build-manifest" = ["bootstrap"] "/src/tools/cargo" = ["@ehuss"] "/src/tools/compiletest" = ["bootstrap", "@wesleywiser", "@oli-obk", "@jieyouxu"] "/src/tools/linkchecker" = ["@ehuss"] From bc611ce5f1d8a5c0b0eac433409da38a43995e22 Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Mon, 19 Jan 2026 09:26:42 -0500 Subject: [PATCH 144/273] Replace version placeholders with 1.94 --- compiler/rustc_feature/src/removed.rs | 4 +-- compiler/rustc_feature/src/unstable.rs | 6 ++-- library/alloc/src/slice.rs | 2 +- library/core/src/cell/lazy.rs | 6 ++-- library/core/src/char/convert.rs | 2 +- library/core/src/iter/adapters/peekable.rs | 4 +-- library/core/src/num/f32.rs | 4 +-- library/core/src/num/f64.rs | 4 +-- library/core/src/slice/iter.rs | 8 ++--- library/core/src/slice/mod.rs | 6 ++-- library/std/src/num/f32.rs | 2 +- library/std/src/num/f64.rs | 2 +- library/std/src/sync/lazy_lock.rs | 6 ++-- library/std_detect/src/detect/arch/riscv.rs | 40 ++++++++++----------- 14 files changed, 48 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 6aaf4542260c..4f4691468911 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -61,7 +61,7 @@ declare_features! ( /// Allows a test to fail without failing the whole suite. (removed, allow_fail, "1.60.0", Some(46488), Some("removed due to no clear use cases"), 93416), /// Allows users to enforce equality of associated constants `TraitImpl`. - (removed, associated_const_equality, "CURRENT_RUSTC_VERSION", Some(92827), + (removed, associated_const_equality, "1.94.0", Some(92827), Some("merged into `min_generic_const_args`")), (removed, await_macro, "1.38.0", Some(50547), Some("subsumed by `.await` syntax"), 62293), @@ -275,7 +275,7 @@ declare_features! ( (removed, static_nobundle, "1.63.0", Some(37403), Some(r#"subsumed by `#[link(kind = "static", modifiers = "-bundle", ...)]`"#), 95818), /// Allows string patterns to dereference values to match them. - (removed, string_deref_patterns, "CURRENT_RUSTC_VERSION", Some(87121), Some("superseded by `deref_patterns`"), 150530), + (removed, string_deref_patterns, "1.94.0", Some(87121), Some("superseded by `deref_patterns`"), 150530), (removed, struct_inherit, "1.0.0", None, None), (removed, test_removed_feature, "1.0.0", None, None), /// Allows using items which are missing stability attributes diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 8959bc586af7..105eb573967d 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -222,7 +222,7 @@ declare_features! ( /// Allows writing custom MIR (internal, custom_mir, "1.65.0", None), /// Implementation details of externally implementable items - (internal, eii_internals, "CURRENT_RUSTC_VERSION", None), + (internal, eii_internals, "1.94.0", None), /// Outputs useful `assert!` messages (unstable, generic_assert, "1.63.0", None), /// Allows using the #[rustc_intrinsic] attribute. @@ -477,7 +477,7 @@ declare_features! ( /// Allows using `#[export_stable]` which indicates that an item is exportable. (incomplete, export_stable, "1.88.0", Some(139939)), /// Externally implementable items - (unstable, extern_item_impls, "CURRENT_RUSTC_VERSION", Some(125418)), + (unstable, extern_item_impls, "1.94.0", Some(125418)), /// Allows defining `extern type`s. (unstable, extern_types, "1.23.0", Some(43467)), /// Allow using 128-bit (quad precision) floating point numbers. @@ -667,7 +667,7 @@ declare_features! ( /// Allows using `try {...}` expressions. (unstable, try_blocks, "1.29.0", Some(31436)), /// Allows using `try bikeshed TargetType {...}` expressions. - (unstable, try_blocks_heterogeneous, "CURRENT_RUSTC_VERSION", Some(149488)), + (unstable, try_blocks_heterogeneous, "1.94.0", Some(149488)), /// Allows `impl Trait` to be used inside type aliases (RFC 2515). (unstable, type_alias_impl_trait, "1.38.0", Some(63063)), /// Allows creation of instances of a struct by moving fields that have diff --git a/library/alloc/src/slice.rs b/library/alloc/src/slice.rs index e7d0fc3454ee..bf5cbafbac63 100644 --- a/library/alloc/src/slice.rs +++ b/library/alloc/src/slice.rs @@ -18,7 +18,7 @@ use core::cmp::Ordering::{self, Less}; use core::mem::MaybeUninit; #[cfg(not(no_global_oom_handling))] use core::ptr; -#[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "array_windows", since = "1.94.0")] pub use core::slice::ArrayWindows; #[stable(feature = "inherent_ascii_escape", since = "1.60.0")] pub use core::slice::EscapeAscii; diff --git a/library/core/src/cell/lazy.rs b/library/core/src/cell/lazy.rs index 8ffa9b3def51..28a76569c1d0 100644 --- a/library/core/src/cell/lazy.rs +++ b/library/core/src/cell/lazy.rs @@ -175,7 +175,7 @@ impl T> LazyCell { /// assert_eq!(*lazy, 44); /// ``` #[inline] - #[stable(feature = "lazy_get", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "lazy_get", since = "1.94.0")] pub fn force_mut(this: &mut LazyCell) -> &mut T { #[cold] /// # Safety @@ -273,7 +273,7 @@ impl LazyCell { /// assert_eq!(*lazy, 44); /// ``` #[inline] - #[stable(feature = "lazy_get", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "lazy_get", since = "1.94.0")] pub fn get_mut(this: &mut LazyCell) -> Option<&mut T> { let state = this.state.get_mut(); match state { @@ -297,7 +297,7 @@ impl LazyCell { /// assert_eq!(LazyCell::get(&lazy), Some(&92)); /// ``` #[inline] - #[stable(feature = "lazy_get", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "lazy_get", since = "1.94.0")] pub fn get(this: &LazyCell) -> Option<&T> { // SAFETY: // This is sound for the same reason as in `force`: once the state is diff --git a/library/core/src/char/convert.rs b/library/core/src/char/convert.rs index 7c1a329e7011..90c09d43f14f 100644 --- a/library/core/src/char/convert.rs +++ b/library/core/src/char/convert.rs @@ -162,7 +162,7 @@ impl const TryFrom for u16 { /// /// Generally speaking, this conversion can be seen as obtaining the character's corresponding /// UTF-32 code point to the extent representable by pointer addresses. -#[stable(feature = "usize_try_from_char", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "usize_try_from_char", since = "1.94.0")] #[rustc_const_unstable(feature = "const_convert", issue = "143773")] impl const TryFrom for usize { type Error = TryFromCharError; diff --git a/library/core/src/iter/adapters/peekable.rs b/library/core/src/iter/adapters/peekable.rs index b9bdb827209b..9f6d1df57dbe 100644 --- a/library/core/src/iter/adapters/peekable.rs +++ b/library/core/src/iter/adapters/peekable.rs @@ -406,7 +406,7 @@ impl Peekable { ///# ], ///# ) /// ``` - #[stable(feature = "peekable_next_if_map", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "peekable_next_if_map", since = "1.94.0")] pub fn next_if_map(&mut self, f: impl FnOnce(I::Item) -> Result) -> Option { let unpeek = if let Some(item) = self.next() { match f(item) { @@ -443,7 +443,7 @@ impl Peekable { /// assert_eq!(line_num, 125); /// assert_eq!(iter.collect::(), " GOTO 10"); /// ``` - #[stable(feature = "peekable_next_if_map", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "peekable_next_if_map", since = "1.94.0")] pub fn next_if_map_mut(&mut self, f: impl FnOnce(&mut I::Item) -> Option) -> Option { let unpeek = if let Some(mut item) = self.next() { match f(&mut item) { diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index f7f16b57a526..3d8249631037 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -292,11 +292,11 @@ pub mod consts { pub const TAU: f32 = 6.28318530717958647692528676655900577_f32; /// The golden ratio (φ) - #[stable(feature = "euler_gamma_golden_ratio", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")] pub const GOLDEN_RATIO: f32 = 1.618033988749894848204586834365638118_f32; /// The Euler-Mascheroni constant (γ) - #[stable(feature = "euler_gamma_golden_ratio", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")] pub const EULER_GAMMA: f32 = 0.577215664901532860606512090082402431_f32; /// π/2 diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index f021c88f2235..566a6a7ec947 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -292,11 +292,11 @@ pub mod consts { pub const TAU: f64 = 6.28318530717958647692528676655900577_f64; /// The golden ratio (φ) - #[stable(feature = "euler_gamma_golden_ratio", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")] pub const GOLDEN_RATIO: f64 = 1.618033988749894848204586834365638118_f64; /// The Euler-Mascheroni constant (γ) - #[stable(feature = "euler_gamma_golden_ratio", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "euler_gamma_golden_ratio", since = "1.94.0")] pub const EULER_GAMMA: f64 = 0.577215664901532860606512090082402431_f64; /// π/2 diff --git a/library/core/src/slice/iter.rs b/library/core/src/slice/iter.rs index 0ddf94559e80..a289b0d6df40 100644 --- a/library/core/src/slice/iter.rs +++ b/library/core/src/slice/iter.rs @@ -2176,7 +2176,7 @@ unsafe impl Sync for ChunksExactMut<'_, T> where T: Sync {} /// [`array_windows`]: slice::array_windows /// [slices]: slice #[derive(Debug, Clone, Copy)] -#[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "array_windows", since = "1.94.0")] #[must_use = "iterators are lazy and do nothing unless consumed"] pub struct ArrayWindows<'a, T: 'a, const N: usize> { v: &'a [T], @@ -2189,7 +2189,7 @@ impl<'a, T: 'a, const N: usize> ArrayWindows<'a, T, N> { } } -#[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "array_windows", since = "1.94.0")] impl<'a, T, const N: usize> Iterator for ArrayWindows<'a, T, N> { type Item = &'a [T; N]; @@ -2226,7 +2226,7 @@ impl<'a, T, const N: usize> Iterator for ArrayWindows<'a, T, N> { } } -#[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "array_windows", since = "1.94.0")] impl<'a, T, const N: usize> DoubleEndedIterator for ArrayWindows<'a, T, N> { #[inline] fn next_back(&mut self) -> Option<&'a [T; N]> { @@ -2245,7 +2245,7 @@ impl<'a, T, const N: usize> DoubleEndedIterator for ArrayWindows<'a, T, N> { } } -#[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "array_windows", since = "1.94.0")] impl ExactSizeIterator for ArrayWindows<'_, T, N> { fn is_empty(&self) -> bool { self.v.len() < N diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index a2d3fa401b62..3e1eeba4e92e 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -52,7 +52,7 @@ pub use ascii::is_ascii_simple; pub use index::SliceIndex; #[unstable(feature = "slice_range", issue = "76393")] pub use index::{range, try_range}; -#[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "array_windows", since = "1.94.0")] pub use iter::ArrayWindows; #[stable(feature = "slice_group_by", since = "1.77.0")] pub use iter::{ChunkBy, ChunkByMut}; @@ -1639,7 +1639,7 @@ impl [T] { /// ``` /// /// [`windows`]: slice::windows - #[stable(feature = "array_windows", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "array_windows", since = "1.94.0")] #[rustc_const_unstable(feature = "const_slice_make_iter", issue = "137737")] #[inline] #[track_caller] @@ -5045,7 +5045,7 @@ impl [T] { /// assert_eq!(arr.element_offset(weird_elm), None); // Points between element 0 and 1 /// ``` #[must_use] - #[stable(feature = "element_offset", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "element_offset", since = "1.94.0")] pub fn element_offset(&self, element: &T) -> Option { if T::IS_ZST { panic!("elements are zero-sized"); diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs index 4126080c1552..77e682478460 100644 --- a/library/std/src/num/f32.rs +++ b/library/std/src/num/f32.rs @@ -217,7 +217,7 @@ impl f32 { #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] #[inline] - #[rustc_const_stable(feature = "const_mul_add", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_mul_add", since = "1.94.0")] pub const fn mul_add(self, a: f32, b: f32) -> f32 { core::f32::math::mul_add(self, a, b) } diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs index e4c388addd9a..e0b9948a924d 100644 --- a/library/std/src/num/f64.rs +++ b/library/std/src/num/f64.rs @@ -217,7 +217,7 @@ impl f64 { #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] #[inline] - #[rustc_const_stable(feature = "const_mul_add", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_mul_add", since = "1.94.0")] pub const fn mul_add(self, a: f64, b: f64) -> f64 { core::f64::math::mul_add(self, a, b) } diff --git a/library/std/src/sync/lazy_lock.rs b/library/std/src/sync/lazy_lock.rs index ef5c949e471f..7274b5d10c75 100644 --- a/library/std/src/sync/lazy_lock.rs +++ b/library/std/src/sync/lazy_lock.rs @@ -182,7 +182,7 @@ impl T> LazyLock { /// assert_eq!(*lazy, 44); /// ``` #[inline] - #[stable(feature = "lazy_get", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "lazy_get", since = "1.94.0")] pub fn force_mut(this: &mut LazyLock) -> &mut T { #[cold] /// # Safety @@ -288,7 +288,7 @@ impl LazyLock { /// assert_eq!(*lazy, 44); /// ``` #[inline] - #[stable(feature = "lazy_get", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "lazy_get", since = "1.94.0")] pub fn get_mut(this: &mut LazyLock) -> Option<&mut T> { // `state()` does not perform an atomic load, so prefer it over `is_complete()`. let state = this.once.state(); @@ -315,7 +315,7 @@ impl LazyLock { /// assert_eq!(LazyLock::get(&lazy), Some(&92)); /// ``` #[inline] - #[stable(feature = "lazy_get", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "lazy_get", since = "1.94.0")] #[rustc_should_not_be_called_on_const_items] pub fn get(this: &LazyLock) -> Option<&T> { if this.once.is_completed() { diff --git a/library/std_detect/src/detect/arch/riscv.rs b/library/std_detect/src/detect/arch/riscv.rs index ecad1a58d5f2..0e6bab512ac1 100644 --- a/library/std_detect/src/detect/arch/riscv.rs +++ b/library/std_detect/src/detect/arch/riscv.rs @@ -196,26 +196,26 @@ features! { @FEATURE: #[unstable(feature = "stdarch_riscv_feature_detection", issue = "111192")] unaligned_vector_mem: "unaligned-vector-mem"; /// Has reasonably performant unaligned vector - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zicsr: "zicsr"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zicsr: "zicsr"; /// "Zicsr" Extension for Control and Status Register (CSR) Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zicntr: "zicntr"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zicntr: "zicntr"; /// "Zicntr" Extension for Base Counters and Timers - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zihpm: "zihpm"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zihpm: "zihpm"; /// "Zihpm" Extension for Hardware Performance Counters - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zifencei: "zifencei"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zifencei: "zifencei"; /// "Zifencei" Extension for Instruction-Fetch Fence - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zihintntl: "zihintntl"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zihintntl: "zihintntl"; /// "Zihintntl" Extension for Non-Temporal Locality Hints - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zihintpause: "zihintpause"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zihintpause: "zihintpause"; /// "Zihintpause" Extension for Pause Hint - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zimop: "zimop"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zimop: "zimop"; /// "Zimop" Extension for May-Be-Operations - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zicbom: "zicbom"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zicbom: "zicbom"; /// "Zicbom" Extension for Cache-Block Management Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zicboz: "zicboz"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zicboz: "zicboz"; /// "Zicboz" Extension for Cache-Block Zero Instruction - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zicond: "zicond"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zicond: "zicond"; /// "Zicond" Extension for Integer Conditional Operations @FEATURE: #[stable(feature = "riscv_ratified", since = "1.78.0")] m: "m"; @@ -223,20 +223,20 @@ features! { @FEATURE: #[stable(feature = "riscv_ratified", since = "1.78.0")] a: "a"; /// "A" Extension for Atomic Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zalrsc: "zalrsc"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zalrsc: "zalrsc"; /// "Zalrsc" Extension for Load-Reserved/Store-Conditional Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zaamo: "zaamo"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zaamo: "zaamo"; /// "Zaamo" Extension for Atomic Memory Operations - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zawrs: "zawrs"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zawrs: "zawrs"; /// "Zawrs" Extension for Wait-on-Reservation-Set Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zabha: "zabha"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zabha: "zabha"; /// "Zabha" Extension for Byte and Halfword Atomic Memory Operations - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zacas: "zacas"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zacas: "zacas"; /// "Zacas" Extension for Atomic Compare-and-Swap (CAS) Instructions @FEATURE: #[unstable(feature = "stdarch_riscv_feature_detection", issue = "111192")] zam: "zam"; without cfg check: true; /// "Zam" Extension for Misaligned Atomics - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] ztso: "ztso"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] ztso: "ztso"; /// "Ztso" Extension for Total Store Ordering @FEATURE: #[unstable(feature = "stdarch_riscv_feature_detection", issue = "111192")] f: "f"; @@ -266,7 +266,7 @@ features! { @FEATURE: #[stable(feature = "riscv_ratified", since = "1.78.0")] c: "c"; /// "C" Extension for Compressed Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zca: "zca"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zca: "zca"; /// "Zca" Compressed Instructions excluding Floating-Point Loads/Stores @FEATURE: #[unstable(feature = "stdarch_riscv_feature_detection", issue = "111192")] zcf: "zcf"; without cfg check: true; @@ -274,12 +274,12 @@ features! { @FEATURE: #[unstable(feature = "stdarch_riscv_feature_detection", issue = "111192")] zcd: "zcd"; without cfg check: true; /// "Zcd" Compressed Instructions for Double-Precision Floating-Point Loads/Stores - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zcb: "zcb"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zcb: "zcb"; /// "Zcb" Simple Code-size Saving Compressed Instructions - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] zcmop: "zcmop"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] zcmop: "zcmop"; /// "Zcmop" Extension for Compressed May-Be-Operations - @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "CURRENT_RUSTC_VERSION")] b: "b"; + @FEATURE: #[stable(feature = "riscv_ratified_v2", since = "1.94.0")] b: "b"; /// "B" Extension for Bit Manipulation @FEATURE: #[stable(feature = "riscv_ratified", since = "1.78.0")] zba: "zba"; /// "Zba" Extension for Address Generation From 47f347476544f8909428299bbcb3e580f4185185 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Wed, 21 Jan 2026 05:03:27 +0000 Subject: [PATCH 145/273] Prepare for merging from rust-lang/rust This updates the rust-version file to d276646872981067251b0fe70131561f4a4142d8. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index f805775e2768..ad29181437d5 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -63f4513795b198d034f5d19962659ea488163755 +d276646872981067251b0fe70131561f4a4142d8 From 91a0e09d3e328dc0eaacf3de733beb0c0347c665 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 21 Jan 2026 18:09:51 +1100 Subject: [PATCH 146/273] Derive `Default` for `QueryArenas` --- compiler/rustc_middle/src/query/plumbing.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index df333e68add1..be7a459d4bb1 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -342,6 +342,7 @@ macro_rules! define_callbacks { })* } + #[derive(Default)] pub struct QueryArenas<'tcx> { $($(#[$attr])* pub $name: query_if_arena!([$($modifiers)*] (TypedArena<<$V as $crate::query::arena_cached::ArenaCached<'tcx>>::Allocated>) @@ -349,17 +350,6 @@ macro_rules! define_callbacks { ),)* } - impl Default for QueryArenas<'_> { - fn default() -> Self { - Self { - $($name: query_if_arena!([$($modifiers)*] - (Default::default()) - () - ),)* - } - } - } - #[derive(Default)] pub struct QueryCaches<'tcx> { $($(#[$attr])* pub $name: queries::$name::Storage<'tcx>,)* From 2495e5834bd166fc5b2fab99f3bcae7ca96fbcd8 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 21 Jan 2026 18:10:53 +1100 Subject: [PATCH 147/273] Add some comments to `QueryArenas` --- compiler/rustc_middle/src/query/plumbing.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index be7a459d4bb1..16121c38d1a9 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -342,12 +342,18 @@ macro_rules! define_callbacks { })* } + /// Holds per-query arenas for queries with the `arena_cache` modifier. #[derive(Default)] pub struct QueryArenas<'tcx> { - $($(#[$attr])* pub $name: query_if_arena!([$($modifiers)*] - (TypedArena<<$V as $crate::query::arena_cached::ArenaCached<'tcx>>::Allocated>) - () - ),)* + $( + $(#[$attr])* + pub $name: query_if_arena!([$($modifiers)*] + // Use the `ArenaCached` helper trait to determine the arena's value type. + (TypedArena<<$V as $crate::query::arena_cached::ArenaCached<'tcx>>::Allocated>) + // No arena for this query, so the field type is `()`. + () + ), + )* } #[derive(Default)] From f4834cf52e38ffac51d1a2a6d47257013c4e84d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 21 Jan 2026 09:44:18 +0100 Subject: [PATCH 148/273] Make the GCC component as nightly only in build-manifest --- src/tools/build-manifest/src/main.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index cd6efad01141..946962832aef 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -31,8 +31,16 @@ static DOCS_FALLBACK: &[(&str, &str)] = &[ static PKG_INSTALLERS: &[&str] = &["x86_64-apple-darwin", "aarch64-apple-darwin"]; -static NIGHTLY_ONLY_COMPONENTS: &[PkgType] = - &[PkgType::Miri, PkgType::JsonDocs, PkgType::RustcCodegenCranelift, PkgType::RustcCodegenGcc]; +fn is_nightly_only(pkg: &PkgType) -> bool { + match pkg { + PkgType::Miri + | PkgType::JsonDocs + | PkgType::RustcCodegenCranelift + | PkgType::RustcCodegenGcc + | PkgType::Gcc { .. } => true, + _ => false, + } +} macro_rules! t { ($e:expr) => { @@ -375,7 +383,7 @@ impl Builder { let mut is_present = version_info.present; // Never ship nightly-only components for other trains. - if self.versions.channel() != "nightly" && NIGHTLY_ONLY_COMPONENTS.contains(&pkg) { + if self.versions.channel() != "nightly" && is_nightly_only(&pkg) { is_present = false; // Pretend the component is entirely missing. } From c3f309e32b24cbbb6cac314869515e0c4b856592 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Mon, 19 Jan 2026 16:15:16 -0800 Subject: [PATCH 149/273] Use `repeat_packed` when calculating layouts in `RawVec` --- library/alloc/src/raw_vec/mod.rs | 8 ++++++-- tests/codegen-llvm/iter-repeat-n-trivial-drop.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/library/alloc/src/raw_vec/mod.rs b/library/alloc/src/raw_vec/mod.rs index 1e76710d3536..ff996ba93cd7 100644 --- a/library/alloc/src/raw_vec/mod.rs +++ b/library/alloc/src/raw_vec/mod.rs @@ -865,9 +865,13 @@ const fn handle_error(e: TryReserveError) -> ! { #[inline] #[rustc_const_unstable(feature = "const_heap", issue = "79597")] const fn layout_array(cap: usize, elem_layout: Layout) -> Result { + // This is only used with `elem_layout`s which are those of real rust types, + // which lets us use the much-simpler `repeat_packed`. + debug_assert!(elem_layout.size() == elem_layout.pad_to_align().size()); + // FIXME(const-hack) return to using `map` and `map_err` once `const_closures` is implemented - match elem_layout.repeat(cap) { - Ok((layout, _pad)) => Ok(layout), + match elem_layout.repeat_packed(cap) { + Ok(layout) => Ok(layout), Err(_) => Err(CapacityOverflow.into()), } } diff --git a/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs b/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs index 1da7de535f75..a4e5c885a139 100644 --- a/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs +++ b/tests/codegen-llvm/iter-repeat-n-trivial-drop.rs @@ -47,7 +47,7 @@ pub fn iter_repeat_n_next(it: &mut std::iter::RepeatN) -> Option Vec { - // CHECK: %[[ADDR:.+]] = tail call {{(noalias )?}}noundef dereferenceable_or_null(1234) ptr @{{.*}}__rust_alloc(i64 noundef {{(range\(i64 1, 0\) )?}}1234, i64 noundef {{(range\(i64 1, -9223372036854775807\) )?}}1) + // CHECK: %[[ADDR:.+]] = tail call {{(noalias )?}}noundef dereferenceable_or_null(1234) ptr @{{.*}}__rust_alloc(i64 noundef {{(range\(i64 0, -9223372036854775808\) )?}}1234, i64 noundef {{(range\(i64 1, -9223372036854775807\) )?}}1) // CHECK: tail call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(1234) %[[ADDR]], i8 42, i64 1234, let n = 1234_usize; From 443765e3946cbbd47b1556088641c23a1424cb48 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 21 Jan 2026 11:51:01 +0100 Subject: [PATCH 150/273] std: use `clock_nanosleep` for `sleep` where available --- library/std/src/sys/thread/unix.rs | 58 ++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index b1f27c32fedd..c3d3d78cf1a8 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -528,8 +528,51 @@ pub fn set_name(name: &CStr) { debug_assert_eq!(res, libc::OK); } -#[cfg(not(any(target_os = "espidf", target_os = "wasi")))] +#[cfg(not(target_os = "espidf"))] pub fn sleep(dur: Duration) { + cfg_select! { + // Any unix that has clock_nanosleep + // If this list changes update the MIRI chock_nanosleep shim + any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "dragonfly", + target_os = "hurd", + target_os = "fuchsia", + target_os = "vxworks", + target_os = "wasi", + ) => { + // POSIX specifies that `nanosleep` uses CLOCK_REALTIME, but is not + // affected by clock adjustments. The timing of `sleep` however should + // be tied to `Instant` where possible. Thus, we use `clock_nanosleep` + // with a relative time interval instead, which allows explicitly + // specifying the clock. + // + // In practice, most systems (like e.g. Linux) actually use + // CLOCK_MONOTONIC for `nanosleep` anyway, but others like FreeBSD don't + // so it's better to be safe. + // + // wasi-libc prior to WebAssembly/wasi-libc#696 has a broken implementation + // of `nanosleep` which used `CLOCK_REALTIME` even though it is unsupported + // on WASIp2. Using `clock_nanosleep` directly bypasses the issue. + unsafe fn nanosleep(rqtp: *const libc::timespec, rmtp: *mut libc::timespec) -> libc::c_int { + unsafe { libc::clock_nanosleep(crate::sys::time::Instant::CLOCK_ID, 0, rqtp, rmtp) } + } + } + _ => { + unsafe fn nanosleep(rqtp: *const libc::timespec, rmtp: *mut libc::timespec) -> libc::c_int { + let r = unsafe { libc::nanosleep(rqtp, rmtp) }; + // `clock_nanosleep` returns the error number directly, so mimic + // that behaviour to make the shared code below simpler. + if r == 0 { 0 } else { sys::io::errno() } + } + } + } + let mut secs = dur.as_secs(); let mut nsecs = dur.subsec_nanos() as _; @@ -543,8 +586,9 @@ pub fn sleep(dur: Duration) { }; secs -= ts.tv_sec as u64; let ts_ptr = &raw mut ts; - if libc::nanosleep(ts_ptr, ts_ptr) == -1 { - assert_eq!(sys::io::errno(), libc::EINTR); + let r = nanosleep(ts_ptr, ts_ptr); + if r != 0 { + assert_eq!(r, libc::EINTR); secs += ts.tv_sec as u64; nsecs = ts.tv_nsec; } else { @@ -554,13 +598,7 @@ pub fn sleep(dur: Duration) { } } -#[cfg(any( - target_os = "espidf", - // wasi-libc prior to WebAssembly/wasi-libc#696 has a broken implementation - // of `nanosleep`, used above by most platforms, so use `usleep` until - // that fix propagates throughout the ecosystem. - target_os = "wasi", -))] +#[cfg(target_os = "espidf")] pub fn sleep(dur: Duration) { // ESP-IDF does not have `nanosleep`, so we use `usleep` instead. // As per the documentation of `usleep`, it is expected to support From cb4c9d30e9dbd80b35cd5d7e1f5415c8407c7f18 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:00:10 +0000 Subject: [PATCH 151/273] Use allocator_shim_contents in allocator_shim_symbols --- compiler/rustc_codegen_ssa/src/back/linker.rs | 4 ++-- compiler/rustc_codegen_ssa/src/back/lto.rs | 6 ++++-- .../rustc_codegen_ssa/src/back/symbol_export.rs | 15 ++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 637d54dd06c6..dd266c5638ad 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -1827,9 +1827,9 @@ fn exported_symbols_for_non_proc_macro( // Mark allocator shim symbols as exported only if they were generated. if export_threshold == SymbolExportLevel::Rust && needs_allocator_shim_for_linking(tcx.dependency_formats(()), crate_type) - && tcx.allocator_kind(()).is_some() + && let Some(kind) = tcx.allocator_kind(()) { - symbols.extend(allocator_shim_symbols(tcx)); + symbols.extend(allocator_shim_symbols(tcx, kind)); } symbols diff --git a/compiler/rustc_codegen_ssa/src/back/lto.rs b/compiler/rustc_codegen_ssa/src/back/lto.rs index ef4c193c4c2a..a79b5c7a1e99 100644 --- a/compiler/rustc_codegen_ssa/src/back/lto.rs +++ b/compiler/rustc_codegen_ssa/src/back/lto.rs @@ -118,8 +118,10 @@ pub(super) fn exported_symbols_for_lto( } // Mark allocator shim symbols as exported only if they were generated. - if export_threshold == SymbolExportLevel::Rust && allocator_kind_for_codegen(tcx).is_some() { - symbols_below_threshold.extend(allocator_shim_symbols(tcx).map(|(name, _kind)| name)); + if export_threshold == SymbolExportLevel::Rust + && let Some(kind) = allocator_kind_for_codegen(tcx) + { + symbols_below_threshold.extend(allocator_shim_symbols(tcx, kind).map(|(name, _kind)| name)); } symbols_below_threshold diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 59d0f5ee9d54..71d03081dc4f 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -1,9 +1,7 @@ use std::collections::hash_map::Entry::*; use rustc_abi::{CanonAbi, X86Call}; -use rustc_ast::expand::allocator::{ - ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, NO_ALLOC_SHIM_IS_UNSTABLE, global_fn_name, -}; +use rustc_ast::expand::allocator::{AllocatorKind, NO_ALLOC_SHIM_IS_UNSTABLE, global_fn_name}; use rustc_data_structures::unord::UnordMap; use rustc_hir::def::DefKind; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LOCAL_CRATE, LocalDefId}; @@ -21,6 +19,7 @@ use rustc_target::spec::{Arch, Os, TlsModel}; use tracing::debug; use crate::back::symbol_export; +use crate::base::allocator_shim_contents; fn threshold(tcx: TyCtxt<'_>) -> SymbolExportLevel { crates_export_threshold(tcx.crate_types()) @@ -490,14 +489,12 @@ pub(crate) fn provide(providers: &mut Providers) { pub(crate) fn allocator_shim_symbols( tcx: TyCtxt<'_>, + kind: AllocatorKind, ) -> impl Iterator { - ALLOCATOR_METHODS - .iter() + allocator_shim_contents(tcx, kind) + .into_iter() .map(move |method| mangle_internal_symbol(tcx, global_fn_name(method.name).as_str())) - .chain([ - mangle_internal_symbol(tcx, global_fn_name(ALLOC_ERROR_HANDLER).as_str()), - mangle_internal_symbol(tcx, NO_ALLOC_SHIM_IS_UNSTABLE), - ]) + .chain([mangle_internal_symbol(tcx, NO_ALLOC_SHIM_IS_UNSTABLE)]) .map(move |symbol_name| { let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, &symbol_name)); From 8d524f096db5ab28defe57d37f95d2ec2f0d0324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 21 Jan 2026 12:53:44 +0100 Subject: [PATCH 152/273] mGCA: Make traits with type assoc consts dyn compatible... ...but require all assoc consts to be specified via bindings. --- .../src/hir_ty_lowering/dyn_trait.rs | 82 +++++++++---------- compiler/rustc_middle/src/traits/mod.rs | 40 ++++++--- compiler/rustc_middle/src/ty/assoc.rs | 9 +- compiler/rustc_middle/src/ty/sty.rs | 5 +- .../src/traits/dyn_compatibility.rs | 44 ++++++---- .../associated-const-in-trait.rs | 2 - .../associated-const-in-trait.stderr | 40 +-------- tests/ui/associated-item/issue-48027.stderr | 6 +- tests/ui/async-await/async-fn/dyn-pos.stderr | 2 +- .../dyn-compat-basic.rs | 38 +++++++++ .../dyn-compat-const-mismatch.rs | 18 ++++ .../dyn-compat-const-mismatch.stderr | 13 +++ .../dyn-compat-generic-assoc-const.rs | 16 ++++ .../dyn-compat-generic-assoc-const.stderr | 19 +++++ .../dyn-compat-non-type-assoc-const.rs | 20 +++++ .../dyn-compat-non-type-assoc-const.stderr | 43 ++++++++++ .../dyn-compat-unspecified-assoc-consts.rs | 25 ++++++ ...dyn-compat-unspecified-assoc-consts.stderr | 30 +++++++ .../issues/cg-in-dyn-issue-128176.stderr | 2 +- .../associated-consts.stderr | 6 +- .../gat-incompatible-supertrait.stderr | 2 +- .../missing-assoc-type.stderr | 2 +- .../no-duplicate-e0038.stderr | 2 +- .../supertrait-mentions-GAT.stderr | 2 +- .../gat-in-trait-path.stderr | 4 +- .../issue-67510-pass.stderr | 2 +- .../issue-76535.stderr | 2 +- .../issue-78671.stderr | 2 +- .../issue-79422.stderr | 2 +- .../trait-objects.stderr | 2 +- tests/ui/traits/item-privacy.stderr | 12 +-- tests/ui/wf/issue-87495.stderr | 11 +-- 32 files changed, 359 insertions(+), 146 deletions(-) create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-basic.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.stderr create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.stderr create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index 2aed66d27e96..695a0793ccd1 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -56,7 +56,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } let mut user_written_bounds = Vec::new(); - let mut potential_assoc_types = Vec::new(); + let mut potential_assoc_items = Vec::new(); for poly_trait_ref in hir_bounds.iter() { let result = self.lower_poly_trait_ref( poly_trait_ref, @@ -66,7 +66,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { OverlappingAsssocItemConstraints::Forbidden, ); if let Err(GenericArgCountMismatch { invalid_args, .. }) = result.correct { - potential_assoc_types.extend(invalid_args); + potential_assoc_items.extend(invalid_args); } } @@ -138,7 +138,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } // Map the projection bounds onto a key that makes it easy to remove redundant - // bounds that are constrained by supertraits of the principal def id. + // bounds that are constrained by supertraits of the principal trait. // // Also make sure we detect conflicting bounds from expanding a trait alias and // also specifying it manually, like: @@ -191,13 +191,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let principal_trait = regular_traits.into_iter().next(); - // A stable ordering of associated types from the principal trait and all its - // supertraits. We use this to ensure that different substitutions of a trait - // don't result in `dyn Trait` types with different projections lists, which - // can be unsound: . - // We achieve a stable ordering by walking over the unsubstituted principal - // trait ref. - let mut ordered_associated_types = vec![]; + // A stable ordering of associated types & consts from the principal trait and all its + // supertraits. We use this to ensure that different substitutions of a trait don't + // result in `dyn Trait` types with different projections lists, which can be unsound: + // . + // We achieve a stable ordering by walking over the unsubstituted principal trait ref. + let mut ordered_associated_items = vec![]; if let Some((principal_trait, ref spans)) = principal_trait { let principal_trait = principal_trait.map_bound(|trait_pred| { @@ -223,12 +222,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // FIXME(negative_bounds): Handle this correctly... let trait_ref = tcx.anonymize_bound_vars(bound_predicate.rebind(pred.trait_ref)); - ordered_associated_types.extend( + ordered_associated_items.extend( tcx.associated_items(pred.trait_ref.def_id) .in_definition_order() - // We only care about associated types. - .filter(|item| item.is_type()) - // No RPITITs -- they're not dyn-compatible for now. + // Only associated types & consts can possibly be constrained via a binding. + .filter(|item| item.is_type() || item.is_const()) + // Traits with RPITITs are simply not dyn compatible (for now). .filter(|item| !item.is_impl_trait_in_trait()) .map(|item| (item.def_id, trait_ref)), ); @@ -283,15 +282,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } - // `dyn Trait` desugars to (not Rust syntax) `dyn Trait where - // ::Assoc = Foo`. So every `Projection` clause is an - // `Assoc = Foo` bound. `needed_associated_types` contains all associated - // types that we expect to be provided by the user, so the following loop - // removes all the associated types that have a corresponding `Projection` - // clause, either from expanding trait aliases or written by the user. + // Flag assoc item bindings that didn't really need to be specified. for &(projection_bound, span) in projection_bounds.values() { let def_id = projection_bound.item_def_id(); if tcx.generics_require_sized_self(def_id) { + // FIXME(mgca): Ideally we would generalize the name of this lint to sth. like + // `unused_associated_item_bindings` since this can now also trigger on *const* + // projections / assoc *const* bindings. tcx.emit_node_span_lint( UNUSED_ASSOCIATED_TYPE_BOUNDS, hir_id, @@ -301,35 +298,36 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } - // We compute the list of projection bounds taking the ordered associated types, - // and check if there was an entry in the collected `projection_bounds`. Those - // are computed by first taking the user-written associated types, then elaborating - // the principal trait ref, and only using those if there was no user-written. - // See note below about how we handle missing associated types with `Self: Sized`, - // which are not required to be provided, but are still used if they are provided. - let mut missing_assoc_types = FxIndexSet::default(); - let projection_bounds: Vec<_> = ordered_associated_types + // The user has to constrain all associated types & consts via bindings unless the + // corresponding associated item has a `where Self: Sized` clause. This can be done + // in the `dyn Trait` directly, in the supertrait bounds or behind trait aliases. + // + // Collect all associated items that weren't specified and compute the list of + // projection bounds which we'll later turn into existential ones. + // + // We intentionally keep around projections whose associated item has a `Self: Sized` + // bound in order to be able to wfcheck the RHS, allow the RHS to constrain generic + // parameters and to imply bounds. + // See also . + let mut missing_assoc_items = FxIndexSet::default(); + let projection_bounds: Vec<_> = ordered_associated_items .into_iter() - .filter_map(|key| { - if let Some(assoc) = projection_bounds.get(&key) { - Some(*assoc) - } else { - // If the associated type has a `where Self: Sized` bound, then - // we do not need to provide the associated type. This results in - // a `dyn Trait` type that has a different number of projection - // bounds, which may lead to type mismatches. - if !tcx.generics_require_sized_self(key.0) { - missing_assoc_types.insert(key); - } - None + .filter_map(|key @ (def_id, _)| { + if let Some(&assoc) = projection_bounds.get(&key) { + return Some(assoc); } + if !tcx.generics_require_sized_self(def_id) { + missing_assoc_items.insert(key); + } + None }) .collect(); + // If there are any associated items whose value wasn't provided, bail out with an error. if let Err(guar) = self.check_for_required_assoc_tys( principal_trait.as_ref().map_or(smallvec![], |(_, spans)| spans.clone()), - missing_assoc_types, - potential_assoc_types, + missing_assoc_items, + potential_assoc_items, hir_bounds, ) { return Ty::new_error(tcx, guar); diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index d71df36684f5..429db832884b 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -759,7 +759,7 @@ pub struct ImplSourceUserDefinedData<'tcx, N> { pub nested: ThinVec, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable)] pub enum DynCompatibilityViolation { /// `Self: Sized` declared on the trait. SizedSelf(SmallVec<[Span; 1]>), @@ -780,11 +780,17 @@ pub enum DynCompatibilityViolation { /// Method has something illegal. Method(Symbol, MethodViolationCode, Span), - /// Associated const. + /// Associated constant. AssocConst(Symbol, Span), - /// GAT - GAT(Symbol, Span), + /// Generic associated constant. + GenericAssocConst(Symbol, Span), + + /// Associated constant that wasn't marked `#[type_const]`. + NonTypeAssocConst(Symbol, Span), + + /// Generic associated type. + GenericAssocTy(Symbol, Span), } impl DynCompatibilityViolation { @@ -852,14 +858,18 @@ impl DynCompatibilityViolation { MethodViolationCode::UndispatchableReceiver(_), _, ) => format!("method `{name}`'s `self` parameter cannot be dispatched on").into(), - DynCompatibilityViolation::AssocConst(name, DUMMY_SP) => { - format!("it contains associated `const` `{name}`").into() + DynCompatibilityViolation::AssocConst(name, _) => { + format!("it contains associated const `{name}`").into() } - DynCompatibilityViolation::AssocConst(..) => { - "it contains this associated `const`".into() + DynCompatibilityViolation::GenericAssocConst(name, _) => { + format!("it contains generic associated const `{name}`").into() } - DynCompatibilityViolation::GAT(name, _) => { - format!("it contains the generic associated type `{name}`").into() + DynCompatibilityViolation::NonTypeAssocConst(name, _) => { + format!("it contains associated const `{name}` that's not marked `#[type_const]`") + .into() + } + DynCompatibilityViolation::GenericAssocTy(name, _) => { + format!("it contains generic associated type `{name}`").into() } } } @@ -888,7 +898,9 @@ impl DynCompatibilityViolation { _, ) => DynCompatibilityViolationSolution::ChangeToRefSelf(*name, *span), DynCompatibilityViolation::AssocConst(name, _) - | DynCompatibilityViolation::GAT(name, _) + | DynCompatibilityViolation::GenericAssocConst(name, _) + | DynCompatibilityViolation::NonTypeAssocConst(name, _) + | DynCompatibilityViolation::GenericAssocTy(name, _) | DynCompatibilityViolation::Method(name, ..) => { DynCompatibilityViolationSolution::MoveToAnotherTrait(*name) } @@ -905,7 +917,9 @@ impl DynCompatibilityViolation { | DynCompatibilityViolation::SupertraitNonLifetimeBinder(spans) | DynCompatibilityViolation::SupertraitConst(spans) => spans.clone(), DynCompatibilityViolation::AssocConst(_, span) - | DynCompatibilityViolation::GAT(_, span) + | DynCompatibilityViolation::GenericAssocConst(_, span) + | DynCompatibilityViolation::NonTypeAssocConst(_, span) + | DynCompatibilityViolation::GenericAssocTy(_, span) | DynCompatibilityViolation::Method(_, _, span) => { if *span != DUMMY_SP { smallvec![*span] @@ -972,7 +986,7 @@ impl DynCompatibilityViolationSolution { } /// Reasons a method might not be dyn-compatible. -#[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable)] pub enum MethodViolationCode { /// e.g., `fn foo()` StaticMethod(Option<(/* add &self */ (String, Span), /* add Self: Sized */ (String, Span))>), diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 5e20bc142ffe..0957d103d997 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -148,8 +148,9 @@ impl AssocItem { AssocKind::Type { .. } => DefKind::AssocTy, } } - pub fn is_type(&self) -> bool { - matches!(self.kind, ty::AssocKind::Type { .. }) + + pub fn is_const(&self) -> bool { + matches!(self.kind, ty::AssocKind::Const { .. }) } pub fn is_fn(&self) -> bool { @@ -160,6 +161,10 @@ impl AssocItem { matches!(self.kind, ty::AssocKind::Fn { has_self: true, .. }) } + pub fn is_type(&self) -> bool { + matches!(self.kind, ty::AssocKind::Type { .. }) + } + pub fn as_tag(&self) -> AssocTag { match self.kind { AssocKind::Const { .. } => AssocTag::Const, diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 34aca8adb4a0..b0b5a783b00e 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -762,8 +762,7 @@ impl<'tcx> Ty<'tcx> { .principal_def_id() .into_iter() .flat_map(|principal_def_id| { - // NOTE: This should agree with `needed_associated_types` in - // dyn trait lowering, or else we'll have ICEs. + // IMPORTANT: This has to agree with HIR ty lowering of dyn trait! elaborate::supertraits( tcx, ty::Binder::dummy(ty::TraitRef::identity(tcx, principal_def_id)), @@ -771,7 +770,7 @@ impl<'tcx> Ty<'tcx> { .map(|principal| { tcx.associated_items(principal.def_id()) .in_definition_order() - .filter(|item| item.is_type()) + .filter(|item| item.is_type() || item.is_const()) .filter(|item| !item.is_impl_trait_in_trait()) .filter(|item| !tcx.generics_require_sized_self(item.def_id)) .count() diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 13acdad8aad7..4ec435009997 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -7,8 +7,9 @@ use std::ops::ControlFlow; use rustc_errors::FatalError; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, LangItem}; +use rustc_hir::{self as hir, LangItem, find_attr}; use rustc_middle::query::Providers; use rustc_middle::ty::{ self, EarlyBinder, GenericArgs, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, @@ -288,7 +289,7 @@ fn generics_require_sized_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool { return false; /* No Sized trait, can't require it! */ }; - // Search for a predicate like `Self : Sized` amongst the trait bounds. + // Search for a predicate like `Self: Sized` amongst the trait bounds. let predicates = tcx.predicates_of(def_id); let predicates = predicates.instantiate_identity(tcx).predicates; elaborate(tcx, predicates).any(|pred| match pred.kind().skip_binder() { @@ -306,24 +307,35 @@ fn generics_require_sized_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool { }) } -/// Returns `Some(_)` if this item makes the containing trait dyn-incompatible. #[instrument(level = "debug", skip(tcx), ret)] pub fn dyn_compatibility_violations_for_assoc_item( tcx: TyCtxt<'_>, trait_def_id: DefId, item: ty::AssocItem, ) -> Vec { - // Any item that has a `Self : Sized` requisite is otherwise - // exempt from the regulations. + // Any item that has a `Self: Sized` requisite is otherwise exempt from the regulations. if tcx.generics_require_sized_self(item.def_id) { return Vec::new(); } + let span = || item.ident(tcx).span; + match item.kind { - // Associated consts are never dyn-compatible, as they can't have `where` bounds yet at all, - // and associated const bounds in trait objects aren't a thing yet either. ty::AssocKind::Const { name } => { - vec![DynCompatibilityViolation::AssocConst(name, item.ident(tcx).span)] + if tcx.features().min_generic_const_args() { + if !tcx.generics_of(item.def_id).is_own_empty() { + vec![DynCompatibilityViolation::GenericAssocConst(name, span())] + } else if !find_attr!(tcx.get_all_attrs(item.def_id), AttributeKind::TypeConst(_)) { + vec![DynCompatibilityViolation::NonTypeAssocConst(name, span())] + } else { + // We will permit type associated consts if they are explicitly mentioned in the + // trait object type. We can't check this here, as here we only check if it is + // guaranteed to not be possible. + Vec::new() + } + } else { + vec![DynCompatibilityViolation::AssocConst(name, span())] + } } ty::AssocKind::Fn { name, .. } => { virtual_call_violations_for_method(tcx, trait_def_id, item) @@ -338,20 +350,22 @@ pub fn dyn_compatibility_violations_for_assoc_item( (MethodViolationCode::ReferencesSelfOutput, Some(node)) => { node.fn_decl().map_or(item.ident(tcx).span, |decl| decl.output.span()) } - _ => item.ident(tcx).span, + _ => span(), }; DynCompatibilityViolation::Method(name, v, span) }) .collect() } - // Associated types can only be dyn-compatible if they have `Self: Sized` bounds. - ty::AssocKind::Type { .. } => { - if !tcx.generics_of(item.def_id).is_own_empty() && !item.is_impl_trait_in_trait() { - vec![DynCompatibilityViolation::GAT(item.name(), item.ident(tcx).span)] + ty::AssocKind::Type { data } => { + if !tcx.generics_of(item.def_id).is_own_empty() + && let ty::AssocTypeData::Normal(name) = data + { + vec![DynCompatibilityViolation::GenericAssocTy(name, span())] } else { - // We will permit associated types if they are explicitly mentioned in the trait object. - // We can't check this here, as here we only check if it is guaranteed to not be possible. + // We will permit associated types if they are explicitly mentioned in the trait + // object type. We can't check this here, as here we only check if it is + // guaranteed to not be possible. Vec::new() } } diff --git a/tests/ui/associated-consts/associated-const-in-trait.rs b/tests/ui/associated-consts/associated-const-in-trait.rs index 4d88f4ff5316..6b0b43feb109 100644 --- a/tests/ui/associated-consts/associated-const-in-trait.rs +++ b/tests/ui/associated-consts/associated-const-in-trait.rs @@ -7,8 +7,6 @@ trait Trait { impl dyn Trait { //~^ ERROR the trait `Trait` is not dyn compatible [E0038] const fn n() -> usize { Self::N } - //~^ ERROR the trait `Trait` is not dyn compatible [E0038] - //~| ERROR the trait `Trait` is not dyn compatible } fn main() {} diff --git a/tests/ui/associated-consts/associated-const-in-trait.stderr b/tests/ui/associated-consts/associated-const-in-trait.stderr index fba7f53c097f..fb4a55110b4e 100644 --- a/tests/ui/associated-consts/associated-const-in-trait.stderr +++ b/tests/ui/associated-consts/associated-const-in-trait.stderr @@ -1,8 +1,8 @@ error[E0038]: the trait `Trait` is not dyn compatible - --> $DIR/associated-const-in-trait.rs:7:6 + --> $DIR/associated-const-in-trait.rs:7:10 | LL | impl dyn Trait { - | ^^^^^^^^^ `Trait` is not dyn compatible + | ^^^^^ `Trait` is not dyn compatible | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit @@ -11,41 +11,9 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Trait { | ----- this trait is not dyn compatible... LL | const N: usize; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `N` = help: consider moving `N` to another trait -error[E0038]: the trait `Trait` is not dyn compatible - --> $DIR/associated-const-in-trait.rs:9:29 - | -LL | const fn n() -> usize { Self::N } - | ^^^^ `Trait` is not dyn compatible - | -note: for a trait to be dyn compatible it needs to allow building a vtable - for more information, visit - --> $DIR/associated-const-in-trait.rs:4:11 - | -LL | trait Trait { - | ----- this trait is not dyn compatible... -LL | const N: usize; - | ^ ...because it contains this associated `const` - = help: consider moving `N` to another trait - -error[E0038]: the trait `Trait` is not dyn compatible - --> $DIR/associated-const-in-trait.rs:9:29 - | -LL | const fn n() -> usize { Self::N } - | ^^^^^^^ `Trait` is not dyn compatible - | -note: for a trait to be dyn compatible it needs to allow building a vtable - for more information, visit - --> $DIR/associated-const-in-trait.rs:4:11 - | -LL | trait Trait { - | ----- this trait is not dyn compatible... -LL | const N: usize; - | ^ ...because it contains this associated `const` - = help: consider moving `N` to another trait - -error: aborting due to 3 previous errors +error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/associated-item/issue-48027.stderr b/tests/ui/associated-item/issue-48027.stderr index e5c1ced93413..7abcabc1c79d 100644 --- a/tests/ui/associated-item/issue-48027.stderr +++ b/tests/ui/associated-item/issue-48027.stderr @@ -1,8 +1,8 @@ error[E0038]: the trait `Bar` is not dyn compatible - --> $DIR/issue-48027.rs:6:6 + --> $DIR/issue-48027.rs:6:10 | LL | impl dyn Bar {} - | ^^^^^^^ `Bar` is not dyn compatible + | ^^^ `Bar` is not dyn compatible | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Bar { | --- this trait is not dyn compatible... LL | const X: usize; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `X` = help: consider moving `X` to another trait error[E0790]: cannot refer to the associated constant on trait without specifying the corresponding `impl` type diff --git a/tests/ui/async-await/async-fn/dyn-pos.stderr b/tests/ui/async-await/async-fn/dyn-pos.stderr index 7d5b37bdbe73..efd3357ca679 100644 --- a/tests/ui/async-await/async-fn/dyn-pos.stderr +++ b/tests/ui/async-await/async-fn/dyn-pos.stderr @@ -8,7 +8,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit --> $SRC_DIR/core/src/ops/async_function.rs:LL:COL | - = note: the trait is not dyn compatible because it contains the generic associated type `CallRefFuture` + = note: the trait is not dyn compatible because it contains generic associated type `CallRefFuture` error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-basic.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-basic.rs new file mode 100644 index 000000000000..6e35d56dc6e7 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-basic.rs @@ -0,0 +1,38 @@ +// Traits with type associated consts are dyn compatible. +// Check that we allow the corresp. trait object types if all assoc consts are specified. + +//@ check-pass + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait: SuperTrait { + #[type_const] + const K: usize; +} + +trait SuperTrait { + #[type_const] + const Q: usize; + #[type_const] + const C: usize; +} + +trait Bound { + #[type_const] + const N: usize; +} + +impl Bound for () { + #[type_const] + const N: usize = 10; +} + +fn main() { + let _: dyn Trait; + + let obj: &dyn Bound = &(); + _ = identity(obj); + + fn identity(x: &(impl ?Sized + Bound)) -> &(impl ?Sized + Bound) { x } +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.rs new file mode 100644 index 000000000000..be41906a4f2f --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.rs @@ -0,0 +1,18 @@ +// Ensure that we actually enforce equality constraints found in trait object types. + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + #[type_const] + const N: usize; +} + +impl Trait for () { + #[type_const] + const N: usize = 1; +} + +fn main() { + let _: &dyn Trait = &(); //~ ERROR type mismatch resolving `<() as Trait>::N == 0` +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.stderr new file mode 100644 index 000000000000..2ab02068eb34 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-mismatch.stderr @@ -0,0 +1,13 @@ +error[E0271]: type mismatch resolving `<() as Trait>::N == 0` + --> $DIR/dyn-compat-const-mismatch.rs:17:32 + | +LL | let _: &dyn Trait = &(); + | ^^^ expected `0`, found `1` + | + = note: expected constant `0` + found constant `1` + = note: required for the cast from `&()` to `&dyn Trait` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0271`. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.rs new file mode 100644 index 000000000000..05e8aeea5ed5 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.rs @@ -0,0 +1,16 @@ +// Ensure that traits with generic associated consts (GACs) are dyn *in*compatible. +// It would be very hard to make dyn Trait with GACs sound just like with GATs. + +//@ dont-require-annotations: NOTE + +#![feature(min_generic_const_args, generic_const_items)] +#![expect(incomplete_features)] + +trait Trait { + const POLY: T; + //~^ NOTE it contains generic associated const `POLY` +} + +fn main() { + let _: dyn Trait; //~ ERROR the trait `Trait` is not dyn compatible +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.stderr new file mode 100644 index 000000000000..ed187e0287e9 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-generic-assoc-const.stderr @@ -0,0 +1,19 @@ +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-generic-assoc-const.rs:15:16 + | +LL | let _: dyn Trait; + | ^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-generic-assoc-const.rs:10:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const POLY: T; + | ^^^^ ...because it contains generic associated const `POLY` + = help: consider moving `POLY` to another trait + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs new file mode 100644 index 000000000000..67650bce8c89 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs @@ -0,0 +1,20 @@ +// Ensure that traits with non-type associated consts are dyn *in*compatible. + +//@ dont-require-annotations: NOTE + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + const K: usize; + //~^ NOTE it contains associated const `K` that's not marked `#[type_const]` +} + +fn main() { + let _: dyn Trait; //~ ERROR the trait `Trait` is not dyn compatible + + // Check that specifying the non-type assoc const doesn't "magically make it work". + let _: dyn Trait; + //~^ ERROR the trait `Trait` is not dyn compatible + //~| ERROR use of trait associated const without `#[type_const]` +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr new file mode 100644 index 000000000000..c579cd312f1f --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr @@ -0,0 +1,43 @@ +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-non-type-assoc-const.rs:14:16 + | +LL | let _: dyn Trait; + | ^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-non-type-assoc-const.rs:9:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const K: usize; + | ^ ...because it contains associated const `K` that's not marked `#[type_const]` + = help: consider moving `K` to another trait + +error: use of trait associated const without `#[type_const]` + --> $DIR/dyn-compat-non-type-assoc-const.rs:17:22 + | +LL | let _: dyn Trait; + | ^^^^^ + | + = note: the declaration in the trait must be marked with `#[type_const]` + +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-non-type-assoc-const.rs:17:16 + | +LL | let _: dyn Trait; + | ^^^^^^^^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-non-type-assoc-const.rs:9:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const K: usize; + | ^ ...because it contains associated const `K` that's not marked `#[type_const]` + = help: consider moving `K` to another trait + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs new file mode 100644 index 000000000000..532c853c32b8 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs @@ -0,0 +1,25 @@ +// Traits with type associated consts are dyn compatible. However, all associated consts must +// be specified in the corresp. trait object type (barring exceptions) similiar to associated +// types. Check that we reject code that doesn't provide the necessary bindings. + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + #[type_const] + const K: usize; +} + +// fn ctxt / body +fn main() { + let _: dyn Trait; + //~^ ERROR the value of the associated type `K` in `Trait` must be specified +} + +// item ctxt / signature / non-body +struct Store(dyn Trait); +//~^ ERROR the value of the associated type `K` in `Trait` must be specified + +// item ctxt & no wfcking (eager ty alias) +type DynTrait = dyn Trait; +//~^ ERROR the value of the associated type `K` in `Trait` must be specified diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr new file mode 100644 index 000000000000..37a4fdd38d3b --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr @@ -0,0 +1,30 @@ +error[E0191]: the value of the associated type `K` in `Trait` must be specified + --> $DIR/dyn-compat-unspecified-assoc-consts.rs:20:18 + | +LL | const K: usize; + | -------------- `K` defined here +... +LL | struct Store(dyn Trait); + | ^^^^^ help: specify the associated type: `Trait` + +error[E0191]: the value of the associated type `K` in `Trait` must be specified + --> $DIR/dyn-compat-unspecified-assoc-consts.rs:24:21 + | +LL | const K: usize; + | -------------- `K` defined here +... +LL | type DynTrait = dyn Trait; + | ^^^^^ help: specify the associated type: `Trait` + +error[E0191]: the value of the associated type `K` in `Trait` must be specified + --> $DIR/dyn-compat-unspecified-assoc-consts.rs:15:16 + | +LL | const K: usize; + | -------------- `K` defined here +... +LL | let _: dyn Trait; + | ^^^^^ help: specify the associated type: `Trait` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0191`. diff --git a/tests/ui/const-generics/issues/cg-in-dyn-issue-128176.stderr b/tests/ui/const-generics/issues/cg-in-dyn-issue-128176.stderr index 7d563e3b6054..87ec5973fa28 100644 --- a/tests/ui/const-generics/issues/cg-in-dyn-issue-128176.stderr +++ b/tests/ui/const-generics/issues/cg-in-dyn-issue-128176.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait X { | - this trait is not dyn compatible... LL | type Y; - | ^ ...because it contains the generic associated type `Y` + | ^ ...because it contains generic associated type `Y` = help: consider moving `Y` to another trait error: aborting due to 1 previous error diff --git a/tests/ui/dyn-compatibility/associated-consts.stderr b/tests/ui/dyn-compatibility/associated-consts.stderr index dc64c93a577e..a92557ea7b8b 100644 --- a/tests/ui/dyn-compatibility/associated-consts.stderr +++ b/tests/ui/dyn-compatibility/associated-consts.stderr @@ -1,8 +1,8 @@ error[E0038]: the trait `Bar` is not dyn compatible - --> $DIR/associated-consts.rs:8:31 + --> $DIR/associated-consts.rs:8:35 | LL | fn make_bar(t: &T) -> &dyn Bar { - | ^^^^^^^ `Bar` is not dyn compatible + | ^^^ `Bar` is not dyn compatible | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Bar { | --- this trait is not dyn compatible... LL | const X: usize; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `X` = help: consider moving `X` to another trait error: aborting due to 1 previous error diff --git a/tests/ui/dyn-compatibility/gat-incompatible-supertrait.stderr b/tests/ui/dyn-compatibility/gat-incompatible-supertrait.stderr index cfebd5d69470..1189c8dc2a6a 100644 --- a/tests/ui/dyn-compatibility/gat-incompatible-supertrait.stderr +++ b/tests/ui/dyn-compatibility/gat-incompatible-supertrait.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Super { | ----- this trait is not dyn compatible... LL | type Assoc<'a>; - | ^^^^^ ...because it contains the generic associated type `Assoc` + | ^^^^^ ...because it contains generic associated type `Assoc` = help: consider moving `Assoc` to another trait error: aborting due to 1 previous error diff --git a/tests/ui/dyn-compatibility/missing-assoc-type.stderr b/tests/ui/dyn-compatibility/missing-assoc-type.stderr index 5a7560682f2e..5a5dc20590d0 100644 --- a/tests/ui/dyn-compatibility/missing-assoc-type.stderr +++ b/tests/ui/dyn-compatibility/missing-assoc-type.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Foo { | --- this trait is not dyn compatible... LL | type Bar; - | ^^^ ...because it contains the generic associated type `Bar` + | ^^^ ...because it contains generic associated type `Bar` = help: consider moving `Bar` to another trait error: aborting due to 1 previous error diff --git a/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr b/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr index 94037387c3e1..bef580bcedb5 100644 --- a/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr +++ b/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Tr { | -- this trait is not dyn compatible... LL | const N: usize; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `N` = help: consider moving `N` to another trait = help: only type `u8` implements `Tr`; consider using it directly instead. diff --git a/tests/ui/dyn-compatibility/supertrait-mentions-GAT.stderr b/tests/ui/dyn-compatibility/supertrait-mentions-GAT.stderr index ba4ce4753995..a8c2f512a9ad 100644 --- a/tests/ui/dyn-compatibility/supertrait-mentions-GAT.stderr +++ b/tests/ui/dyn-compatibility/supertrait-mentions-GAT.stderr @@ -18,7 +18,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable --> $DIR/supertrait-mentions-GAT.rs:4:10 | LL | type Gat<'a> - | ^^^ ...because it contains the generic associated type `Gat` + | ^^^ ...because it contains generic associated type `Gat` ... LL | trait SuperTrait: for<'a> GatTrait = T> { | ---------- this trait is not dyn compatible... diff --git a/tests/ui/generic-associated-types/gat-in-trait-path.stderr b/tests/ui/generic-associated-types/gat-in-trait-path.stderr index d4ccd80f1465..8dec88f6d932 100644 --- a/tests/ui/generic-associated-types/gat-in-trait-path.stderr +++ b/tests/ui/generic-associated-types/gat-in-trait-path.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Foo { | --- this trait is not dyn compatible... LL | type A<'a> where Self: 'a; - | ^ ...because it contains the generic associated type `A` + | ^ ...because it contains generic associated type `A` = help: consider moving `A` to another trait error[E0038]: the trait `Foo` is not dyn compatible @@ -27,7 +27,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait Foo { | --- this trait is not dyn compatible... LL | type A<'a> where Self: 'a; - | ^ ...because it contains the generic associated type `A` + | ^ ...because it contains generic associated type `A` = help: consider moving `A` to another trait error: aborting due to 2 previous errors diff --git a/tests/ui/generic-associated-types/issue-67510-pass.stderr b/tests/ui/generic-associated-types/issue-67510-pass.stderr index 4b56c4ef35f4..8f47407089c7 100644 --- a/tests/ui/generic-associated-types/issue-67510-pass.stderr +++ b/tests/ui/generic-associated-types/issue-67510-pass.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait X { | - this trait is not dyn compatible... LL | type Y<'a>; - | ^ ...because it contains the generic associated type `Y` + | ^ ...because it contains generic associated type `Y` = help: consider moving `Y` to another trait error: aborting due to 1 previous error diff --git a/tests/ui/generic-associated-types/issue-76535.stderr b/tests/ui/generic-associated-types/issue-76535.stderr index 2daf9d817bb3..324f35e88293 100644 --- a/tests/ui/generic-associated-types/issue-76535.stderr +++ b/tests/ui/generic-associated-types/issue-76535.stderr @@ -27,7 +27,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | pub trait SuperTrait { | ---------- this trait is not dyn compatible... LL | type SubType<'a>: SubTrait where Self: 'a; - | ^^^^^^^ ...because it contains the generic associated type `SubType` + | ^^^^^^^ ...because it contains generic associated type `SubType` = help: consider moving `SubType` to another trait = help: only type `SuperStruct` implements `SuperTrait` within this crate; consider using it directly instead. = note: `SuperTrait` may be implemented in other crates; if you want to support your users passing their own types here, you can't refer to a specific type diff --git a/tests/ui/generic-associated-types/issue-78671.stderr b/tests/ui/generic-associated-types/issue-78671.stderr index fff061a8ada7..8b744622fc81 100644 --- a/tests/ui/generic-associated-types/issue-78671.stderr +++ b/tests/ui/generic-associated-types/issue-78671.stderr @@ -27,7 +27,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait CollectionFamily { | ---------------- this trait is not dyn compatible... LL | type Member; - | ^^^^^^ ...because it contains the generic associated type `Member` + | ^^^^^^ ...because it contains generic associated type `Member` = help: consider moving `Member` to another trait error: aborting due to 2 previous errors diff --git a/tests/ui/generic-associated-types/issue-79422.stderr b/tests/ui/generic-associated-types/issue-79422.stderr index dcf3a9008de5..d22cc48e5290 100644 --- a/tests/ui/generic-associated-types/issue-79422.stderr +++ b/tests/ui/generic-associated-types/issue-79422.stderr @@ -27,7 +27,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait MapLike { | ------- this trait is not dyn compatible... LL | type VRefCont<'a>: RefCont<'a, V> - | ^^^^^^^^ ...because it contains the generic associated type `VRefCont` + | ^^^^^^^^ ...because it contains generic associated type `VRefCont` = help: consider moving `VRefCont` to another trait error: aborting due to 2 previous errors diff --git a/tests/ui/generic-associated-types/trait-objects.stderr b/tests/ui/generic-associated-types/trait-objects.stderr index 8c3af6b654ab..03a7eb76d91b 100644 --- a/tests/ui/generic-associated-types/trait-objects.stderr +++ b/tests/ui/generic-associated-types/trait-objects.stderr @@ -11,7 +11,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait StreamingIterator { | ----------------- this trait is not dyn compatible... LL | type Item<'a> where Self: 'a; - | ^^^^ ...because it contains the generic associated type `Item` + | ^^^^ ...because it contains generic associated type `Item` = help: consider moving `Item` to another trait error: aborting due to 1 previous error diff --git a/tests/ui/traits/item-privacy.stderr b/tests/ui/traits/item-privacy.stderr index eb72b307ed0f..acb67af53f18 100644 --- a/tests/ui/traits/item-privacy.stderr +++ b/tests/ui/traits/item-privacy.stderr @@ -141,15 +141,15 @@ note: for a trait to be dyn compatible it needs to allow building a vtable --> $DIR/item-privacy.rs:26:15 | LL | const A: u8 = 0; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `A` ... LL | const B: u8 = 0; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `B` ... LL | pub trait C: A + B { | - this trait is not dyn compatible... LL | const C: u8 = 0; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `C` = help: consider moving `C` to another trait = help: consider moving `A` to another trait = help: consider moving `B` to another trait @@ -166,15 +166,15 @@ note: for a trait to be dyn compatible it needs to allow building a vtable --> $DIR/item-privacy.rs:26:15 | LL | const A: u8 = 0; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `A` ... LL | const B: u8 = 0; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `B` ... LL | pub trait C: A + B { | - this trait is not dyn compatible... LL | const C: u8 = 0; - | ^ ...because it contains this associated `const` + | ^ ...because it contains associated const `C` = help: consider moving `C` to another trait = help: consider moving `A` to another trait = help: consider moving `B` to another trait diff --git a/tests/ui/wf/issue-87495.stderr b/tests/ui/wf/issue-87495.stderr index bf79535df116..49651e8d6c05 100644 --- a/tests/ui/wf/issue-87495.stderr +++ b/tests/ui/wf/issue-87495.stderr @@ -1,8 +1,8 @@ error[E0038]: the trait `T` is not dyn compatible - --> $DIR/issue-87495.rs:4:25 + --> $DIR/issue-87495.rs:4:29 | LL | const CONST: (bool, dyn T); - | ^^^^^ `T` is not dyn compatible + | ^ `T` is not dyn compatible | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit @@ -11,13 +11,8 @@ note: for a trait to be dyn compatible it needs to allow building a vtable LL | trait T { | - this trait is not dyn compatible... LL | const CONST: (bool, dyn T); - | ^^^^^ ...because it contains this associated `const` + | ^^^^^ ...because it contains associated const `CONST` = help: consider moving `CONST` to another trait -help: you might have meant to use `Self` to refer to the implementing type - | -LL - const CONST: (bool, dyn T); -LL + const CONST: (bool, Self); - | error: aborting due to 1 previous error From 4a6b5edba8dbb3594f3d78aff4b2f3fb206f42ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Thu, 8 Jan 2026 08:51:34 +0100 Subject: [PATCH 153/273] Fix v0 symbol mangling for assoc const bindings --- Cargo.lock | 4 +-- compiler/rustc_symbol_mangling/Cargo.toml | 2 +- compiler/rustc_symbol_mangling/src/v0.rs | 5 +++- src/doc/rustc/src/symbol-mangling/v0.md | 9 ++++-- .../dyn-compat-symbol-mangling.rs | 28 +++++++++++++++++++ .../dyn-compat-symbol-mangling.v0.stderr | 20 +++++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.v0.stderr diff --git a/Cargo.lock b/Cargo.lock index 833356011964..7db952ef55d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3356,9 +3356,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" diff --git a/compiler/rustc_symbol_mangling/Cargo.toml b/compiler/rustc_symbol_mangling/Cargo.toml index 0df9c7682bf8..d28eb3f04706 100644 --- a/compiler/rustc_symbol_mangling/Cargo.toml +++ b/compiler/rustc_symbol_mangling/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start punycode = "0.4.0" -rustc-demangle = "0.1.21" +rustc-demangle = "0.1.27" rustc_abi = { path = "../rustc_abi" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 755b4923e9cd..95cbb9e07ebb 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -648,7 +648,10 @@ impl<'tcx> Printer<'tcx> for V0SymbolMangler<'tcx> { p.push_ident(name.as_str()); match projection.term.kind() { ty::TermKind::Ty(ty) => ty.print(p), - ty::TermKind::Const(c) => c.print(p), + ty::TermKind::Const(c) => { + p.push("K"); + c.print(p) + } }?; } ty::ExistentialPredicate::AutoTrait(def_id) => { diff --git a/src/doc/rustc/src/symbol-mangling/v0.md b/src/doc/rustc/src/symbol-mangling/v0.md index 2bcc453a5326..89d10658bb7e 100644 --- a/src/doc/rustc/src/symbol-mangling/v0.md +++ b/src/doc/rustc/src/symbol-mangling/v0.md @@ -857,7 +857,11 @@ Remaining primitives are encoded as a crate production, e.g. `C4f128`. > > dyn-trait → *[path]* {*[dyn-trait-assoc-binding]*} > - > dyn-trait-assoc-binding → `p` *[undisambiguated-identifier]* *[type]* + > dyn-trait-assoc-binding → `p` *[undisambiguated-identifier]* *[type-or-const]* + > + > type-or-const → \ + >       *[type]* \ + >    | `K` *[const]* The tag `D` is followed by a *[dyn-bounds]* which encodes the trait bounds, followed by a *[lifetime]* of the trait object lifetime bound. @@ -867,11 +871,12 @@ Remaining primitives are encoded as a crate production, e.g. `C4f128`. Each *[dyn-trait]* represents a trait bound, which consists of a *[path]* to the trait followed by zero or more *[dyn-trait-assoc-binding]* which list the associated types. - Each *[dyn-trait-assoc-binding]* consists of a character `p` followed a *[undisambiguated-identifier]* representing the associated binding name, and finally a *[type]*. + Each *[dyn-trait-assoc-binding]* consists of a character `p` followed a *[undisambiguated-identifier]* representing the associated binding name, and finally a *[type-or-const]*. [dyn-bounds]: #dyn-bounds [dyn-trait]: #dyn-trait [dyn-trait-assoc-binding]: #dyn-trait-assoc-binding +[type-or-const]: #type-or-const * A *[path]* to a named type. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.rs new file mode 100644 index 000000000000..18be0fccc051 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.rs @@ -0,0 +1,28 @@ +// Ensure that we can successfully mangle & demangle trait object types w/ assoc const bindings. + +// FIXME(mgca): Legacy mangling still crashes: +// "finding type for [impl], encountered [crate root] with no parent" + +//@ build-fail +//@ revisions: v0 +//\@[legacy] compile-flags: -C symbol-mangling-version=legacy -Z unstable-options +//@ [v0] compile-flags: -C symbol-mangling-version=v0 +//\@[legacy] normalize-stderr: "h[[:xdigit:]]{16}" -> "h[HASH]" +//@ [v0] normalize-stderr: "sym\[.*?\]" -> "sym[HASH]" + +#![feature(min_generic_const_args, rustc_attrs)] +#![expect(incomplete_features)] +#![crate_name = "sym"] + +trait Trait { + #[type_const] + const N: usize; +} + +#[rustc_symbol_name] +//~^ ERROR symbol-name(_RMCs +//~| ERROR demangling(>) +impl dyn Trait {} + +fn main() {} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.v0.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.v0.stderr new file mode 100644 index 000000000000..424251acc3e9 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-symbol-mangling.v0.stderr @@ -0,0 +1,20 @@ +error: symbol-name(_RMCsCRATE_HASH_3symDNtB_5Traitp1NKj0_EL_) + --> $DIR/dyn-compat-symbol-mangling.rs:22:1 + | +LL | #[rustc_symbol_name] + | ^^^^^^^^^^^^^^^^^^^^ + +error: demangling(>) + --> $DIR/dyn-compat-symbol-mangling.rs:22:1 + | +LL | #[rustc_symbol_name] + | ^^^^^^^^^^^^^^^^^^^^ + +error: demangling-alt(>) + --> $DIR/dyn-compat-symbol-mangling.rs:22:1 + | +LL | #[rustc_symbol_name] + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + From 7777958231d3eee422b5c57b82f762d5a96f8bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 5 Jan 2026 00:37:30 +0100 Subject: [PATCH 154/273] mGCA: Permit certain const projections in dyn-compatible traits --- .../src/traits/dyn_compatibility.rs | 127 ++++++++++-------- ...ompat-self-const-projections-in-methods.rs | 56 ++++++++ ...-const-projections-in-supertrait-bounds.rs | 20 +++ ...st-projections-in-supertrait-bounds.stderr | 18 +++ 4 files changed, 162 insertions(+), 59 deletions(-) create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-methods.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.stderr diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 4ec435009997..e8f0dd57353c 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -686,13 +686,16 @@ enum AllowSelfProjections { No, } -/// This is somewhat subtle. In general, we want to forbid -/// references to `Self` in the argument and return types, -/// since the value of `Self` is erased. However, there is one -/// exception: it is ok to reference `Self` in order to access -/// an associated type of the current trait, since we retain -/// the value of those associated types in the object type -/// itself. +/// Check if the given value contains illegal `Self` references. +/// +/// This is somewhat subtle. In general, we want to forbid references to `Self` in the +/// argument and return types, since the value of `Self` is erased. +/// +/// However, there is one exception: It is ok to reference `Self` in order to access an +/// associated type of the current trait, since we retain the value of those associated +/// types in the trait object type itself. +/// +/// The same thing holds for associated consts under feature `min_generic_const_args`. /// /// ```rust,ignore (example) /// trait SuperTrait { @@ -747,82 +750,88 @@ struct IllegalSelfTypeVisitor<'tcx> { allow_self_projections: AllowSelfProjections, } +impl<'tcx> IllegalSelfTypeVisitor<'tcx> { + fn is_supertrait_of_current_trait(&mut self, trait_ref: ty::TraitRef<'tcx>) -> bool { + // Compute supertraits of current trait lazily. + let supertraits = self.supertraits.get_or_insert_with(|| { + util::supertraits( + self.tcx, + ty::Binder::dummy(ty::TraitRef::identity(self.tcx, self.trait_def_id)), + ) + .map(|trait_ref| { + self.tcx.erase_and_anonymize_regions( + self.tcx.instantiate_bound_regions_with_erased(trait_ref), + ) + }) + .collect() + }); + + // Determine whether the given trait ref is in fact a supertrait of the current trait. + // In that case, any derived projections are legal, because the term will be specified + // in the trait object type. + // Note that we can just use direct equality here because all of these types are part of + // the formal parameter listing, and hence there should be no inference variables. + let trait_ref = trait_ref + .fold_with(&mut EraseEscapingBoundRegions { tcx: self.tcx, binder: ty::INNERMOST }); + supertraits.contains(&trait_ref) + } +} + impl<'tcx> TypeVisitor> for IllegalSelfTypeVisitor<'tcx> { type Result = ControlFlow<()>; - fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { - match t.kind() { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + match ty.kind() { ty::Param(_) => { - if t == self.tcx.types.self_param { + if ty == self.tcx.types.self_param { ControlFlow::Break(()) } else { ControlFlow::Continue(()) } } - ty::Alias(ty::Projection, data) if self.tcx.is_impl_trait_in_trait(data.def_id) => { + ty::Alias(ty::Projection, proj) if self.tcx.is_impl_trait_in_trait(proj.def_id) => { // We'll deny these later in their own pass ControlFlow::Continue(()) } - ty::Alias(ty::Projection, data) => { + ty::Alias(ty::Projection, proj) => { match self.allow_self_projections { AllowSelfProjections::Yes => { - // This is a projected type `::X`. - - // Compute supertraits of current trait lazily. - if self.supertraits.is_none() { - self.supertraits = Some( - util::supertraits( - self.tcx, - ty::Binder::dummy(ty::TraitRef::identity( - self.tcx, - self.trait_def_id, - )), - ) - .map(|trait_ref| { - self.tcx.erase_and_anonymize_regions( - self.tcx.instantiate_bound_regions_with_erased(trait_ref), - ) - }) - .collect(), - ); - } - - // Determine whether the trait reference `Foo as - // SomeTrait` is in fact a supertrait of the - // current trait. In that case, this type is - // legal, because the type `X` will be specified - // in the object type. Note that we can just use - // direct equality here because all of these types - // are part of the formal parameter listing, and - // hence there should be no inference variables. - let is_supertrait_of_current_trait = - self.supertraits.as_ref().unwrap().contains( - &data.trait_ref(self.tcx).fold_with( - &mut EraseEscapingBoundRegions { - tcx: self.tcx, - binder: ty::INNERMOST, - }, - ), - ); - - // only walk contained types if it's not a super trait - if is_supertrait_of_current_trait { + // Only walk contained types if the parent trait is not a supertrait. + if self.is_supertrait_of_current_trait(proj.trait_ref(self.tcx)) { ControlFlow::Continue(()) } else { - t.super_visit_with(self) // POSSIBLY reporting an error + ty.super_visit_with(self) } } - AllowSelfProjections::No => t.super_visit_with(self), + AllowSelfProjections::No => ty.super_visit_with(self), } } - _ => t.super_visit_with(self), + _ => ty.super_visit_with(self), } } fn visit_const(&mut self, ct: ty::Const<'tcx>) -> Self::Result { - // Constants can only influence dyn-compatibility if they are generic and reference `Self`. - // This is only possible for unevaluated constants, so we walk these here. - self.tcx.expand_abstract_consts(ct).super_visit_with(self) + let ct = self.tcx.expand_abstract_consts(ct); + + match ct.kind() { + ty::ConstKind::Unevaluated(proj) if self.tcx.features().min_generic_const_args() => { + match self.allow_self_projections { + AllowSelfProjections::Yes => { + let trait_def_id = self.tcx.parent(proj.def); + let trait_ref = ty::TraitRef::from_assoc(self.tcx, trait_def_id, proj.args); + + // Only walk contained consts if the parent trait is not a supertrait. + if self.is_supertrait_of_current_trait(trait_ref) { + ControlFlow::Continue(()) + } else { + ct.super_visit_with(self) + } + } + AllowSelfProjections::No => ct.super_visit_with(self), + } + } + _ => ct.super_visit_with(self), + } } } diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-methods.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-methods.rs new file mode 100644 index 000000000000..f5a5fbc8188c --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-methods.rs @@ -0,0 +1,56 @@ +// While mentioning `Self` in the method signature of dyn compatible traits is generally forbidden +// due to type erasure, we can make an exception for const projections from `Self` where the trait +// is the principal trait or a supertrait thereof. That's sound because we force users to specify +// all associated consts in the trait object type, so the projections are all normalizable. +// +// Check that we can define & use dyn compatible traits that reference `Self` const projections. + +// This is a run-pass test to ensure that codegen can actually deal with such method instances +// (e.g., const projections normalize flawlessly to something concrete, symbols get mangled +// properly, the vtable is fine) and simply to ensure that the generated code "received" the +// correct values from the type assoc consts). +//@ run-pass + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + #[type_const] + const N: usize; + + fn process(&self, _: [u8; Self::N]) -> [u8; Self::N]; +} + +impl Trait for u8 { + #[type_const] + const N: usize = 2; + + fn process(&self, [x, y]: [u8; Self::N]) -> [u8; Self::N] { + [self * x, self + y] + } +} + +impl Trait for [u8; N] { + #[type_const] + const N: usize = N; + + fn process(&self, other: [u8; Self::N]) -> [u8; Self::N] { + let mut result = [0; _]; + for i in 0..Self::N { + result[i] = self[i] + other[i]; + } + result + } +} + +fn main() { + let ops: [Box>; _] = [Box::new(3), Box::new([1, 1])]; + + let mut data = [16, 32]; + + for op in ops { + data = op.process(data); + } + + assert_eq!(data, [49, 36]); +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.rs new file mode 100644 index 000000000000..c8929750f61c --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.rs @@ -0,0 +1,20 @@ +// Ensure that we reject the `Self` type parameter in supertrait bounds of dyn-compatible traits +// even if they're part of a "`Self` projection" (contrary to method signatures and the type of +// assoc consts). + +//@ dont-require-annotations: NOTE + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait: SuperTrait<{ Self::N }> { +//~^ NOTE it uses `Self` as a type parameter + #[type_const] + const N: usize; +} + +trait SuperTrait {} + +fn main() { + let _: dyn Trait; //~ ERROR the trait `Trait` is not dyn compatible +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.stderr new file mode 100644 index 000000000000..33edc96117d5 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-supertrait-bounds.stderr @@ -0,0 +1,18 @@ +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-self-const-projections-in-supertrait-bounds.rs:19:16 + | +LL | let _: dyn Trait; + | ^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-self-const-projections-in-supertrait-bounds.rs:10:14 + | +LL | trait Trait: SuperTrait<{ Self::N }> { + | ----- ^^^^^^^^^^^^^^^^^^^^^^^ ...because it uses `Self` as a type parameter + | | + | this trait is not dyn compatible... + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0038`. From 9b861ace46c4791150d23082016ec21bf944f86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 5 Jan 2026 09:50:38 +0100 Subject: [PATCH 155/273] Generalize diag for missing assoc types to account for assoc consts --- .../src/hir_ty_lowering/dyn_trait.rs | 2 +- .../src/hir_ty_lowering/errors.rs | 230 ++++++++++-------- compiler/rustc_middle/src/ty/assoc.rs | 2 +- .../overlaping-bound-suggestion.stderr | 2 +- .../associated-types-incomplete-object.stderr | 6 +- .../ui/associated-types/issue-23595-1.stderr | 2 +- .../dyn-compat-unspecified-assoc-consts.rs | 6 +- ...dyn-compat-unspecified-assoc-consts.stderr | 12 +- .../assoc_type_bounds.stderr | 4 +- .../assoc_type_bounds2.stderr | 4 +- .../assoc_type_bounds_sized_others.stderr | 4 +- .../require-assoc-for-all-super-substs.stderr | 2 +- tests/ui/error-codes/E0191.stderr | 2 +- tests/ui/error-codes/E0220.stderr | 2 +- ...dynless-turbofish-e0191-issue-91997.stderr | 2 +- .../impl-trait/associated-type-cycle.stderr | 2 +- tests/ui/issues/issue-19482.stderr | 2 +- tests/ui/issues/issue-28344.stderr | 4 +- .../ui/suggestions/trait-hidden-method.stderr | 2 +- tests/ui/traits/alias/object-fail.stderr | 2 +- ...dyn-trait-wo-assoc-type-issue-21950.stderr | 2 +- .../with-self-in-projection-output-bad.stderr | 4 +- ...sociated-type-in-trait-object-22434.stderr | 2 +- 23 files changed, 170 insertions(+), 132 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index 695a0793ccd1..d700bd544b0b 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -324,7 +324,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .collect(); // If there are any associated items whose value wasn't provided, bail out with an error. - if let Err(guar) = self.check_for_required_assoc_tys( + if let Err(guar) = self.check_for_required_assoc_items( principal_trait.as_ref().map_or(smallvec![], |(_, spans)| spans.clone()), missing_assoc_items, potential_assoc_items, diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 92b77a2042b1..f467780143ef 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -909,43 +909,49 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { err.emit() } - /// When there are any missing associated types, emit an E0191 error and attempt to supply a - /// reasonable suggestion on how to write it. For the case of multiple associated types in the - /// same trait bound have the same name (as they come from different supertraits), we instead - /// emit a generic note suggesting using a `where` clause to constraint instead. - pub(crate) fn check_for_required_assoc_tys( + /// If there are any missing associated items, emit an error instructing the user to provide + /// them unless that's impossible due to shadowing. Moreover, if any corresponding trait refs + /// are dyn incompatible due to associated items we emit an dyn incompatibility error instead. + pub(crate) fn check_for_required_assoc_items( &self, spans: SmallVec<[Span; 1]>, - missing_assoc_types: FxIndexSet<(DefId, ty::PolyTraitRef<'tcx>)>, - potential_assoc_types: Vec, + missing_assoc_items: FxIndexSet<(DefId, ty::PolyTraitRef<'tcx>)>, + potential_assoc_items: Vec, trait_bounds: &[hir::PolyTraitRef<'_>], ) -> Result<(), ErrorGuaranteed> { - if missing_assoc_types.is_empty() { + if missing_assoc_items.is_empty() { return Ok(()); } + let tcx = self.tcx(); let principal_span = *spans.first().unwrap(); - let tcx = self.tcx(); // FIXME: This logic needs some more care w.r.t handling of conflicts - let missing_assoc_types: Vec<_> = missing_assoc_types + let missing_assoc_items: Vec<_> = missing_assoc_items .into_iter() .map(|(def_id, trait_ref)| (tcx.associated_item(def_id), trait_ref)) .collect(); - let mut names: FxIndexMap<_, Vec> = Default::default(); + let mut names: FxIndexMap<_, Vec<_>> = Default::default(); let mut names_len = 0; + let mut descr = None; - // Account for things like `dyn Foo + 'a`, like in tests `issue-22434.rs` and - // `issue-22560.rs`. - let mut dyn_compatibility_violations = Ok(()); - for (assoc_item, trait_ref) in &missing_assoc_types { - names.entry(trait_ref).or_default().push(assoc_item.name()); - names_len += 1; + enum Descr { + Item, + Tag(ty::AssocTag), + } + for &(assoc_item, trait_ref) in &missing_assoc_items { + // We don't want to suggest specifying associated items if there's something wrong with + // any of them that renders the trait dyn incompatible; providing them certainly won't + // fix the issue and we could also risk suggesting invalid code. + // + // Note that this check is only truly necessary in item ctxts where we merely perform + // *minimal* dyn compatibility checks. In fn ctxts we would've already bailed out with + // an error by this point if the trait was dyn incompatible. let violations = - dyn_compatibility_violations_for_assoc_item(tcx, trait_ref.def_id(), *assoc_item); + dyn_compatibility_violations_for_assoc_item(tcx, trait_ref.def_id(), assoc_item); if !violations.is_empty() { - dyn_compatibility_violations = Err(report_dyn_incompatibility( + return Err(report_dyn_incompatibility( tcx, principal_span, None, @@ -954,15 +960,20 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ) .emit()); } - } - if let Err(guar) = dyn_compatibility_violations { - return Err(guar); + names.entry(trait_ref).or_default().push(assoc_item.name()); + names_len += 1; + + descr = match descr { + None => Some(Descr::Tag(assoc_item.as_tag())), + Some(Descr::Tag(tag)) if tag != assoc_item.as_tag() => Some(Descr::Item), + _ => continue, + }; } // related to issue #91997, turbofishes added only when in an expr or pat let mut in_expr_or_pat = false; - if let ([], [bound]) = (&potential_assoc_types[..], &trait_bounds) { + if let ([], [bound]) = (&potential_assoc_items[..], &trait_bounds) { let grandparent = tcx.parent_hir_node(tcx.parent_hir_id(bound.trait_ref.hir_ref_id)); in_expr_or_pat = match grandparent { hir::Node::Expr(_) | hir::Node::Pat(_) => true, @@ -970,37 +981,41 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }; } - // We get all the associated items that _are_ set, - // so that we can check if any of their names match one of the ones we are missing. - // This would mean that they are shadowing the associated type we are missing, - // and we can then use their span to indicate this to the user. - let bound_names = trait_bounds - .iter() - .filter_map(|poly_trait_ref| { - let path = poly_trait_ref.trait_ref.path.segments.last()?; - let args = path.args?; + // We get all the associated items that *are* set, so that we can check if any of + // their names match one of the ones we are missing. + // This would mean that they are shadowing the associated item we are missing, and + // we can then use their span to indicate this to the user. + // + // FIXME: This does not account for trait aliases. I think we should just make + // `lower_trait_object_ty` compute the list of all specified items or give us the + // necessary ingredients if it's too expensive to compute in the happy path. + let bound_names: UnordMap<_, _> = + trait_bounds + .iter() + .filter_map(|poly_trait_ref| { + let path = poly_trait_ref.trait_ref.path.segments.last()?; + let args = path.args?; + let Res::Def(DefKind::Trait, trait_def_id) = path.res else { return None }; - Some(args.constraints.iter().filter_map(|constraint| { - let ident = constraint.ident; + Some(args.constraints.iter().filter_map(move |constraint| { + let hir::AssocItemConstraintKind::Equality { term } = constraint.kind + else { + return None; + }; + let tag = match term { + hir::Term::Ty(_) => ty::AssocTag::Type, + hir::Term::Const(_) => ty::AssocTag::Const, + }; + let assoc_item = tcx + .associated_items(trait_def_id) + .find_by_ident_and_kind(tcx, constraint.ident, tag, trait_def_id)?; + Some(((constraint.ident.name, tag), assoc_item.def_id)) + })) + }) + .flatten() + .collect(); - let Res::Def(DefKind::Trait, trait_def) = path.res else { - return None; - }; - - let assoc_item = tcx.associated_items(trait_def).find_by_ident_and_kind( - tcx, - ident, - ty::AssocTag::Type, - trait_def, - ); - - Some((ident.name, assoc_item?)) - })) - }) - .flatten() - .collect::>(); - - let mut names = names + let mut names: Vec<_> = names .into_iter() .map(|(trait_, mut assocs)| { assocs.sort(); @@ -1010,66 +1025,71 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { listify(&assocs[..], |a| format!("`{a}`")).unwrap_or_default() ) }) - .collect::>(); + .collect(); names.sort(); let names = names.join(", "); + let descr = match descr.unwrap() { + // FIXME(fmease): Create `ty::AssocTag::descr`. + Descr::Tag(ty::AssocTag::Type) => "associated type", + Descr::Tag(ty::AssocTag::Const) => "associated constant", + _ => "associated item", + }; let mut err = struct_span_code_err!( self.dcx(), principal_span, E0191, - "the value of the associated type{} {} must be specified", - pluralize!(names_len), - names, + "the value of the {descr}{s} {names} must be specified", + s = pluralize!(names_len), ); let mut suggestions = vec![]; - let mut types_count = 0; + let mut items_count = 0; let mut where_constraints = vec![]; let mut already_has_generics_args_suggestion = false; let mut names: UnordMap<_, usize> = Default::default(); - for (item, _) in &missing_assoc_types { - types_count += 1; - *names.entry(item.name()).or_insert(0) += 1; + for (item, _) in &missing_assoc_items { + items_count += 1; + *names.entry((item.name(), item.as_tag())).or_insert(0) += 1; } let mut dupes = false; let mut shadows = false; - for (item, trait_ref) in &missing_assoc_types { + for (item, trait_ref) in &missing_assoc_items { let name = item.name(); - let prefix = if names[&name] > 1 { - let trait_def_id = trait_ref.def_id(); + let key = (name, item.as_tag()); + + if names[&key] > 1 { dupes = true; - format!("{}::", tcx.def_path_str(trait_def_id)) - } else if bound_names.get(&name).is_some_and(|x| *x != item) { - let trait_def_id = trait_ref.def_id(); + } else if bound_names.get(&key).is_some_and(|&def_id| def_id != item.def_id) { shadows = true; - format!("{}::", tcx.def_path_str(trait_def_id)) + } + + let prefix = if dupes || shadows { + format!("{}::", tcx.def_path_str(trait_ref.def_id())) } else { String::new() }; - let mut is_shadowed = false; - if let Some(assoc_item) = bound_names.get(&name) - && *assoc_item != item + if let Some(&def_id) = bound_names.get(&key) + && def_id != item.def_id { is_shadowed = true; - let rename_message = - if assoc_item.def_id.is_local() { ", consider renaming it" } else { "" }; + let rename_message = if def_id.is_local() { ", consider renaming it" } else { "" }; err.span_label( - tcx.def_span(assoc_item.def_id), - format!("`{}{}` shadowed here{}", prefix, name, rename_message), + tcx.def_span(def_id), + format!("`{prefix}{name}` shadowed here{rename_message}"), ); } let rename_message = if is_shadowed { ", consider renaming it" } else { "" }; if let Some(sp) = tcx.hir_span_if_local(item.def_id) { - err.span_label(sp, format!("`{}{}` defined here{}", prefix, name, rename_message)); + err.span_label(sp, format!("`{prefix}{name}` defined here{rename_message}")); } } - if potential_assoc_types.len() == missing_assoc_types.len() { + if potential_assoc_items.len() == missing_assoc_items.len() { // When the amount of missing associated types equals the number of // extra type arguments present. A suggesting to replace the generic args with // associated types is already emitted. @@ -1077,30 +1097,48 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } else if let (Ok(snippet), false, false) = (tcx.sess.source_map().span_to_snippet(principal_span), dupes, shadows) { - let types: Vec<_> = missing_assoc_types + let bindings: Vec<_> = missing_assoc_items .iter() - .map(|(item, _)| format!("{} = Type", item.name())) + .map(|(item, _)| { + format!( + "{} = /* {} */", + item.name(), + match item.kind { + ty::AssocKind::Const { .. } => "CONST", + ty::AssocKind::Type { .. } => "Type", + ty::AssocKind::Fn { .. } => unreachable!(), + } + ) + }) .collect(); + // FIXME(fmease): Does not account for `dyn Trait<>` (suggs `dyn Trait<, X = Y>`). let code = if let Some(snippet) = snippet.strip_suffix('>') { - // The user wrote `Trait<'a>` or similar and we don't have a type we can - // suggest, but at least we can clue them to the correct syntax - // `Trait<'a, Item = Type>` while accounting for the `<'a>` in the - // suggestion. - format!("{}, {}>", snippet, types.join(", ")) + // The user wrote `Trait<'a>` or similar and we don't have a term we can suggest, + // but at least we can clue them to the correct syntax `Trait<'a, Item = /* ... */>` + // while accounting for the `<'a>` in the suggestion. + format!("{}, {}>", snippet, bindings.join(", ")) } else if in_expr_or_pat { - // The user wrote `Iterator`, so we don't have a type we can suggest, but at - // least we can clue them to the correct syntax `Iterator::`. - format!("{}::<{}>", snippet, types.join(", ")) + // The user wrote `Trait`, so we don't have a term we can suggest, but at least we + // can clue them to the correct syntax `Trait::`. + format!("{}::<{}>", snippet, bindings.join(", ")) } else { - // The user wrote `Iterator`, so we don't have a type we can suggest, but at - // least we can clue them to the correct syntax `Iterator`. - format!("{}<{}>", snippet, types.join(", ")) + // The user wrote `Trait`, so we don't have a term we can suggest, but at least we + // can clue them to the correct syntax `Trait`. + format!("{}<{}>", snippet, bindings.join(", ")) }; suggestions.push((principal_span, code)); } else if dupes { where_constraints.push(principal_span); } + // FIXME: This note doesn't make sense, get rid of this outright. + // I don't see how adding a type param (to the trait?) would help. + // If the user can modify the trait, they should just rename one of the assoc tys. + // What does it mean with the rest of the message? + // Does it suggest adding equality predicates (unimplemented) to the trait object + // type? (pseudo) "dyn B + ::X = T + ::X = U"? + // Instead, maybe mention shadowing if applicable (yes, even when no "relevant" + // bindings were provided). let where_msg = "consider introducing a new type parameter, adding `where` constraints \ using the fully-qualified path to the associated types"; if !where_constraints.is_empty() && suggestions.is_empty() { @@ -1112,12 +1150,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { if suggestions.len() != 1 || already_has_generics_args_suggestion { // We don't need this label if there's an inline suggestion, show otherwise. let mut names: FxIndexMap<_, usize> = FxIndexMap::default(); - for (item, _) in &missing_assoc_types { - types_count += 1; + for (item, _) in &missing_assoc_items { + items_count += 1; *names.entry(item.name()).or_insert(0) += 1; } let mut label = vec![]; - for (item, trait_ref) in &missing_assoc_types { + for (item, trait_ref) in &missing_assoc_items { let name = item.name(); let postfix = if names[&name] > 1 { format!(" (from trait `{}`)", trait_ref.print_trait_sugared()) @@ -1130,9 +1168,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { err.span_label( principal_span, format!( - "associated type{} {} must be specified", - pluralize!(label.len()), - label.join(", "), + "{descr}{s} {names} must be specified", + s = pluralize!(label.len()), + names = label.join(", "), ), ); } @@ -1153,7 +1191,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let overlaps = suggestions.windows(2).any(|pair| pair[0].0.overlaps(pair[1].0)); if !suggestions.is_empty() && !overlaps { err.multipart_suggestion( - format!("specify the associated type{}", pluralize!(types_count)), + format!("specify the {descr}{s}", s = pluralize!(items_count)), suggestions, Applicability::HasPlaceholders, ); diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 0957d103d997..4a54a3109462 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -223,7 +223,7 @@ impl std::fmt::Display for AssocKind { } // Like `AssocKind`, but just the tag, no fields. Used in various kinds of matching. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AssocTag { Const, Fn, diff --git a/tests/ui/associated-type-bounds/overlaping-bound-suggestion.stderr b/tests/ui/associated-type-bounds/overlaping-bound-suggestion.stderr index ad97df377b75..54bcef455746 100644 --- a/tests/ui/associated-type-bounds/overlaping-bound-suggestion.stderr +++ b/tests/ui/associated-type-bounds/overlaping-bound-suggestion.stderr @@ -2,7 +2,7 @@ error[E0191]: the value of the associated types `Item` and `IntoIter` in `IntoIt --> $DIR/overlaping-bound-suggestion.rs:7:13 | LL | inner: >::IntoIterator as Item>::Core, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: specify the associated types: `IntoIterator, Item = Type, IntoIter = Type>` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: specify the associated types: `IntoIterator, Item = /* Type */, IntoIter = /* Type */>` error: aborting due to 1 previous error diff --git a/tests/ui/associated-types/associated-types-incomplete-object.stderr b/tests/ui/associated-types/associated-types-incomplete-object.stderr index 0c9761afeb51..52aa6adc4695 100644 --- a/tests/ui/associated-types/associated-types-incomplete-object.stderr +++ b/tests/ui/associated-types/associated-types-incomplete-object.stderr @@ -5,7 +5,7 @@ LL | type B; | ------ `B` defined here ... LL | let b = &42isize as &dyn Foo; - | ^^^^^^^^^^^^ help: specify the associated type: `Foo` + | ^^^^^^^^^^^^ help: specify the associated type: `Foo` error[E0191]: the value of the associated type `A` in `Foo` must be specified --> $DIR/associated-types-incomplete-object.rs:26:30 @@ -14,7 +14,7 @@ LL | type A; | ------ `A` defined here ... LL | let c = &42isize as &dyn Foo; - | ^^^^^^^^^^^ help: specify the associated type: `Foo` + | ^^^^^^^^^^^ help: specify the associated type: `Foo` error[E0191]: the value of the associated types `A` and `B` in `Foo` must be specified --> $DIR/associated-types-incomplete-object.rs:29:30 @@ -25,7 +25,7 @@ LL | type B; | ------ `B` defined here ... LL | let d = &42isize as &dyn Foo; - | ^^^ help: specify the associated types: `Foo` + | ^^^ help: specify the associated types: `Foo` error: aborting due to 3 previous errors diff --git a/tests/ui/associated-types/issue-23595-1.stderr b/tests/ui/associated-types/issue-23595-1.stderr index 694b68ef0901..8083355deb76 100644 --- a/tests/ui/associated-types/issue-23595-1.stderr +++ b/tests/ui/associated-types/issue-23595-1.stderr @@ -6,7 +6,7 @@ LL | type Value; LL | type ChildKey; | ------------- `ChildKey` defined here LL | type Children = dyn Index; - | ------------- `Children` defined here ^^^^^^^^^ help: specify the associated types: `Hierarchy` + | ------------- `Children` defined here ^^^^^^^^^ help: specify the associated types: `Hierarchy` error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs index 532c853c32b8..3525cbceb87b 100644 --- a/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.rs @@ -13,13 +13,13 @@ trait Trait { // fn ctxt / body fn main() { let _: dyn Trait; - //~^ ERROR the value of the associated type `K` in `Trait` must be specified + //~^ ERROR the value of the associated constant `K` in `Trait` must be specified } // item ctxt / signature / non-body struct Store(dyn Trait); -//~^ ERROR the value of the associated type `K` in `Trait` must be specified +//~^ ERROR the value of the associated constant `K` in `Trait` must be specified // item ctxt & no wfcking (eager ty alias) type DynTrait = dyn Trait; -//~^ ERROR the value of the associated type `K` in `Trait` must be specified +//~^ ERROR the value of the associated constant `K` in `Trait` must be specified diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr index 37a4fdd38d3b..68658a5711cb 100644 --- a/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-unspecified-assoc-consts.stderr @@ -1,29 +1,29 @@ -error[E0191]: the value of the associated type `K` in `Trait` must be specified +error[E0191]: the value of the associated constant `K` in `Trait` must be specified --> $DIR/dyn-compat-unspecified-assoc-consts.rs:20:18 | LL | const K: usize; | -------------- `K` defined here ... LL | struct Store(dyn Trait); - | ^^^^^ help: specify the associated type: `Trait` + | ^^^^^ help: specify the associated constant: `Trait` -error[E0191]: the value of the associated type `K` in `Trait` must be specified +error[E0191]: the value of the associated constant `K` in `Trait` must be specified --> $DIR/dyn-compat-unspecified-assoc-consts.rs:24:21 | LL | const K: usize; | -------------- `K` defined here ... LL | type DynTrait = dyn Trait; - | ^^^^^ help: specify the associated type: `Trait` + | ^^^^^ help: specify the associated constant: `Trait` -error[E0191]: the value of the associated type `K` in `Trait` must be specified +error[E0191]: the value of the associated constant `K` in `Trait` must be specified --> $DIR/dyn-compat-unspecified-assoc-consts.rs:15:16 | LL | const K: usize; | -------------- `K` defined here ... LL | let _: dyn Trait; - | ^^^^^ help: specify the associated type: `Trait` + | ^^^^^ help: specify the associated constant: `Trait` error: aborting due to 3 previous errors diff --git a/tests/ui/dyn-compatibility/assoc_type_bounds.stderr b/tests/ui/dyn-compatibility/assoc_type_bounds.stderr index 21ba90301173..717d8949e2d8 100644 --- a/tests/ui/dyn-compatibility/assoc_type_bounds.stderr +++ b/tests/ui/dyn-compatibility/assoc_type_bounds.stderr @@ -5,7 +5,7 @@ LL | type Bar | -------- `Bar` defined here ... LL | fn foo(_: &dyn Foo<()>) {} - | ^^^^^^^ help: specify the associated type: `Foo<(), Bar = Type>` + | ^^^^^^^ help: specify the associated type: `Foo<(), Bar = /* Type */>` error[E0191]: the value of the associated type `Bar` in `Foo` must be specified --> $DIR/assoc_type_bounds.rs:11:16 @@ -14,7 +14,7 @@ LL | type Bar | -------- `Bar` defined here ... LL | fn bar(_: &dyn Foo) {} - | ^^^^^^^^ help: specify the associated type: `Foo` + | ^^^^^^^^ help: specify the associated type: `Foo` error: aborting due to 2 previous errors diff --git a/tests/ui/dyn-compatibility/assoc_type_bounds2.stderr b/tests/ui/dyn-compatibility/assoc_type_bounds2.stderr index 5c4163b19693..9ddded9addfa 100644 --- a/tests/ui/dyn-compatibility/assoc_type_bounds2.stderr +++ b/tests/ui/dyn-compatibility/assoc_type_bounds2.stderr @@ -5,7 +5,7 @@ LL | type Bar | -------- `Bar` defined here ... LL | fn foo(_: &dyn Foo<()>) {} - | ^^^^^^^ help: specify the associated type: `Foo<(), Bar = Type>` + | ^^^^^^^ help: specify the associated type: `Foo<(), Bar = /* Type */>` error[E0191]: the value of the associated type `Bar` in `Foo` must be specified --> $DIR/assoc_type_bounds2.rs:11:16 @@ -14,7 +14,7 @@ LL | type Bar | -------- `Bar` defined here ... LL | fn bar(_: &dyn Foo) {} - | ^^^^^^^^ help: specify the associated type: `Foo` + | ^^^^^^^^ help: specify the associated type: `Foo` error: aborting due to 2 previous errors diff --git a/tests/ui/dyn-compatibility/assoc_type_bounds_sized_others.stderr b/tests/ui/dyn-compatibility/assoc_type_bounds_sized_others.stderr index 5438faaaf054..8f75f19f571a 100644 --- a/tests/ui/dyn-compatibility/assoc_type_bounds_sized_others.stderr +++ b/tests/ui/dyn-compatibility/assoc_type_bounds_sized_others.stderr @@ -5,7 +5,7 @@ LL | type Bop; | -------- `Bop` defined here ... LL | fn foo(_: &dyn Foo) {} - | ^^^ help: specify the associated type: `Foo` + | ^^^ help: specify the associated type: `Foo` error[E0191]: the value of the associated type `Bop` in `Bar` must be specified --> $DIR/assoc_type_bounds_sized_others.rs:22:16 @@ -14,7 +14,7 @@ LL | type Bop; | -------- `Bop` defined here ... LL | fn bar(_: &dyn Bar) {} - | ^^^ help: specify the associated type: `Bar` + | ^^^ help: specify the associated type: `Bar` error: aborting due to 2 previous errors diff --git a/tests/ui/dyn-compatibility/require-assoc-for-all-super-substs.stderr b/tests/ui/dyn-compatibility/require-assoc-for-all-super-substs.stderr index 3d89b52d522d..f39be936f417 100644 --- a/tests/ui/dyn-compatibility/require-assoc-for-all-super-substs.stderr +++ b/tests/ui/dyn-compatibility/require-assoc-for-all-super-substs.stderr @@ -5,7 +5,7 @@ LL | type Assoc: Default; | ------------------- `Assoc` defined here ... LL | let q: as Sup>::Assoc = Default::default(); - | ^^^^^^^^^^^^^ help: specify the associated type: `Dyn` + | ^^^^^^^^^^^^^ help: specify the associated type: `Dyn` error: aborting due to 1 previous error diff --git a/tests/ui/error-codes/E0191.stderr b/tests/ui/error-codes/E0191.stderr index 63974fd6cbbb..6fbb20839777 100644 --- a/tests/ui/error-codes/E0191.stderr +++ b/tests/ui/error-codes/E0191.stderr @@ -5,7 +5,7 @@ LL | type Bar; | -------- `Bar` defined here ... LL | type Foo = dyn Trait; - | ^^^^^ help: specify the associated type: `Trait` + | ^^^^^ help: specify the associated type: `Trait` error: aborting due to 1 previous error diff --git a/tests/ui/error-codes/E0220.stderr b/tests/ui/error-codes/E0220.stderr index 0e0b5c7084cc..48197c9a8ccf 100644 --- a/tests/ui/error-codes/E0220.stderr +++ b/tests/ui/error-codes/E0220.stderr @@ -11,7 +11,7 @@ LL | type Bar; | -------- `Bar` defined here ... LL | type Foo = dyn Trait; - | ^^^^^^^^^^^^ help: specify the associated type: `Trait` + | ^^^^^^^^^^^^ help: specify the associated type: `Trait` error: aborting due to 2 previous errors diff --git a/tests/ui/errors/dynless-turbofish-e0191-issue-91997.stderr b/tests/ui/errors/dynless-turbofish-e0191-issue-91997.stderr index c1584e66e330..cae414bf1225 100644 --- a/tests/ui/errors/dynless-turbofish-e0191-issue-91997.stderr +++ b/tests/ui/errors/dynless-turbofish-e0191-issue-91997.stderr @@ -16,7 +16,7 @@ error[E0191]: the value of the associated type `Item` in `Iterator` must be spec --> $DIR/dynless-turbofish-e0191-issue-91997.rs:5:13 | LL | let _ = MyIterator::next; - | ^^^^^^^^^^ help: specify the associated type: `MyIterator::` + | ^^^^^^^^^^ help: specify the associated type: `MyIterator::` error: aborting due to 1 previous error; 1 warning emitted diff --git a/tests/ui/impl-trait/associated-type-cycle.stderr b/tests/ui/impl-trait/associated-type-cycle.stderr index 7eef8d1e3389..438a5a92ffc2 100644 --- a/tests/ui/impl-trait/associated-type-cycle.stderr +++ b/tests/ui/impl-trait/associated-type-cycle.stderr @@ -5,7 +5,7 @@ LL | type Bar; | -------- `Bar` defined here ... LL | impl Foo for Box { - | ^^^ help: specify the associated type: `Foo` + | ^^^ help: specify the associated type: `Foo` error: aborting due to 1 previous error diff --git a/tests/ui/issues/issue-19482.stderr b/tests/ui/issues/issue-19482.stderr index 903a9f98c758..7683e16610e6 100644 --- a/tests/ui/issues/issue-19482.stderr +++ b/tests/ui/issues/issue-19482.stderr @@ -5,7 +5,7 @@ LL | type A; | ------ `A` defined here ... LL | fn bar(x: &dyn Foo) {} - | ^^^ help: specify the associated type: `Foo` + | ^^^ help: specify the associated type: `Foo` error: aborting due to 1 previous error diff --git a/tests/ui/issues/issue-28344.stderr b/tests/ui/issues/issue-28344.stderr index c85424e22794..9940f005b7f8 100644 --- a/tests/ui/issues/issue-28344.stderr +++ b/tests/ui/issues/issue-28344.stderr @@ -16,7 +16,7 @@ error[E0191]: the value of the associated type `Output` in `BitXor<_>` must be s --> $DIR/issue-28344.rs:5:17 | LL | let x: u8 = BitXor::bitor(0 as u8, 0 as u8); - | ^^^^^^ help: specify the associated type: `BitXor::` + | ^^^^^^ help: specify the associated type: `BitXor::` warning: trait objects without an explicit `dyn` are deprecated --> $DIR/issue-28344.rs:10:13 @@ -35,7 +35,7 @@ error[E0191]: the value of the associated type `Output` in `BitXor<_>` must be s --> $DIR/issue-28344.rs:10:13 | LL | let g = BitXor::bitor; - | ^^^^^^ help: specify the associated type: `BitXor::` + | ^^^^^^ help: specify the associated type: `BitXor::` error: aborting due to 2 previous errors; 2 warnings emitted diff --git a/tests/ui/suggestions/trait-hidden-method.stderr b/tests/ui/suggestions/trait-hidden-method.stderr index 87753e578462..c764034d8466 100644 --- a/tests/ui/suggestions/trait-hidden-method.stderr +++ b/tests/ui/suggestions/trait-hidden-method.stderr @@ -2,7 +2,7 @@ error[E0191]: the value of the associated type `Item` in `Iterator` must be spec --> $DIR/trait-hidden-method.rs:4:33 | LL | Box::new(1..=10) as Box - | ^^^^^^^^ help: specify the associated type: `Iterator` + | ^^^^^^^^ help: specify the associated type: `Iterator` error: aborting due to 1 previous error diff --git a/tests/ui/traits/alias/object-fail.stderr b/tests/ui/traits/alias/object-fail.stderr index d60d88434077..088af686258a 100644 --- a/tests/ui/traits/alias/object-fail.stderr +++ b/tests/ui/traits/alias/object-fail.stderr @@ -19,7 +19,7 @@ error[E0191]: the value of the associated type `Item` in `Iterator` must be spec --> $DIR/object-fail.rs:9:17 | LL | let _: &dyn IteratorAlias = &vec![123].into_iter(); - | ^^^^^^^^^^^^^ help: specify the associated type: `IteratorAlias` + | ^^^^^^^^^^^^^ help: specify the associated type: `IteratorAlias` error: aborting due to 2 previous errors diff --git a/tests/ui/traits/cast-as-dyn-trait-wo-assoc-type-issue-21950.stderr b/tests/ui/traits/cast-as-dyn-trait-wo-assoc-type-issue-21950.stderr index 5f4974e6f237..6a7d3c850c92 100644 --- a/tests/ui/traits/cast-as-dyn-trait-wo-assoc-type-issue-21950.stderr +++ b/tests/ui/traits/cast-as-dyn-trait-wo-assoc-type-issue-21950.stderr @@ -5,7 +5,7 @@ LL | type Output; | ----------- `Output` defined here ... LL | let x = &10 as &dyn Add; - | ^^^ help: specify the associated type: `Add` + | ^^^ help: specify the associated type: `Add` error: aborting due to 1 previous error diff --git a/tests/ui/traits/object/with-self-in-projection-output-bad.stderr b/tests/ui/traits/object/with-self-in-projection-output-bad.stderr index c9b36e8d29de..d0cc7f7fc924 100644 --- a/tests/ui/traits/object/with-self-in-projection-output-bad.stderr +++ b/tests/ui/traits/object/with-self-in-projection-output-bad.stderr @@ -5,7 +5,7 @@ LL | type Output; | ----------- `Output` defined here ... LL | let _x: Box> = Box::new(2u32); - | ^^^^^^^^^^^^^^^^^^ help: specify the associated type: `Helper` + | ^^^^^^^^^^^^^^^^^^ help: specify the associated type: `Helper` error[E0191]: the value of the associated type `Output` in `Base` must be specified --> $DIR/with-self-in-projection-output-bad.rs:48:21 @@ -14,7 +14,7 @@ LL | type Output; | ----------- `Output` defined here ... LL | let _y: Box> = Box::new(2u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: specify the associated type: `NormalizableHelper` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: specify the associated type: `NormalizableHelper` error: aborting due to 2 previous errors diff --git a/tests/ui/type-alias/missing-associated-type-in-trait-object-22434.stderr b/tests/ui/type-alias/missing-associated-type-in-trait-object-22434.stderr index 73afefa5a1fd..c198b83e9688 100644 --- a/tests/ui/type-alias/missing-associated-type-in-trait-object-22434.stderr +++ b/tests/ui/type-alias/missing-associated-type-in-trait-object-22434.stderr @@ -5,7 +5,7 @@ LL | type A; | ------ `A` defined here ... LL | type I<'a> = &'a (dyn Foo + 'a); - | ^^^ help: specify the associated type: `Foo` + | ^^^ help: specify the associated type: `Foo` error: aborting due to 1 previous error From 7c8210ea1fc3c91d4a817f19faddcdaf523c6966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 5 Jan 2026 10:06:33 +0100 Subject: [PATCH 156/273] Generalize diag for conflicting assoc type bindings to account for assoc consts --- .../src/hir_ty_lowering/dyn_trait.rs | 18 +++++++++--------- .../duplicate-bound-err.rs | 4 ++-- .../duplicate-bound-err.stderr | 4 ++-- .../associated-types-overridden-binding-2.rs | 2 +- ...ssociated-types-overridden-binding-2.stderr | 2 +- .../associated-types-overridden-binding.rs | 2 +- .../associated-types-overridden-binding.stderr | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index d700bd544b0b..f4002396b9d8 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -161,8 +161,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { b }); + let item_def_id = proj.item_def_id(); + let key = ( - proj.skip_binder().projection_term.def_id, + item_def_id, tcx.anonymize_bound_vars( proj.map_bound(|proj| proj.projection_term.trait_ref(tcx)), ), @@ -171,19 +173,17 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { projection_bounds.insert(key, (proj, proj_span)) && tcx.anonymize_bound_vars(proj) != tcx.anonymize_bound_vars(old_proj) { - let item = tcx.item_name(proj.item_def_id()); + let kind = tcx.def_descr(item_def_id); + let name = tcx.item_name(item_def_id); self.dcx() - .struct_span_err( - span, - format!("conflicting associated type bounds for `{item}`"), - ) + .struct_span_err(span, format!("conflicting {kind} bindings for `{name}`")) .with_span_label( old_proj_span, - format!("`{item}` is specified to be `{}` here", old_proj.term()), + format!("`{name}` is specified to be `{}` here", old_proj.term()), ) .with_span_label( proj_span, - format!("`{item}` is specified to be `{}` here", proj.term()), + format!("`{name}` is specified to be `{}` here", proj.term()), ) .emit(); } @@ -261,7 +261,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // the discussion in #56288 for alternatives. if !references_self { let key = ( - pred.skip_binder().projection_term.def_id, + pred.item_def_id(), tcx.anonymize_bound_vars( pred.map_bound(|proj| proj.projection_term.trait_ref(tcx)), ), diff --git a/tests/ui/associated-type-bounds/duplicate-bound-err.rs b/tests/ui/associated-type-bounds/duplicate-bound-err.rs index 1587be7200e7..07eb86108bff 100644 --- a/tests/ui/associated-type-bounds/duplicate-bound-err.rs +++ b/tests/ui/associated-type-bounds/duplicate-bound-err.rs @@ -86,7 +86,7 @@ fn uncallable_rtn( type MustFail = dyn Iterator; //~^ ERROR [E0719] -//~| ERROR conflicting associated type bounds +//~| ERROR conflicting associated type bindings trait Trait2 { #[type_const] @@ -95,7 +95,7 @@ trait Trait2 { type MustFail2 = dyn Trait2; //~^ ERROR [E0719] -//~| ERROR conflicting associated type bounds +//~| ERROR conflicting associated constant bindings type MustFail3 = dyn Iterator; //~^ ERROR [E0719] diff --git a/tests/ui/associated-type-bounds/duplicate-bound-err.stderr b/tests/ui/associated-type-bounds/duplicate-bound-err.stderr index ec864c1dd96e..80db2cdb933f 100644 --- a/tests/ui/associated-type-bounds/duplicate-bound-err.stderr +++ b/tests/ui/associated-type-bounds/duplicate-bound-err.stderr @@ -116,7 +116,7 @@ LL | type MustFail = dyn Iterator; | | | `Item` bound here first -error: conflicting associated type bounds for `Item` +error: conflicting associated type bindings for `Item` --> $DIR/duplicate-bound-err.rs:87:17 | LL | type MustFail = dyn Iterator; @@ -133,7 +133,7 @@ LL | type MustFail2 = dyn Trait2; | | | `ASSOC` bound here first -error: conflicting associated type bounds for `ASSOC` +error: conflicting associated constant bindings for `ASSOC` --> $DIR/duplicate-bound-err.rs:96:18 | LL | type MustFail2 = dyn Trait2; diff --git a/tests/ui/associated-types/associated-types-overridden-binding-2.rs b/tests/ui/associated-types/associated-types-overridden-binding-2.rs index 247724eaaf11..fe8041363246 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding-2.rs +++ b/tests/ui/associated-types/associated-types-overridden-binding-2.rs @@ -4,5 +4,5 @@ trait I32Iterator = Iterator; fn main() { let _: &dyn I32Iterator = &vec![42].into_iter(); - //~^ ERROR conflicting associated type bounds + //~^ ERROR conflicting associated type bindings } diff --git a/tests/ui/associated-types/associated-types-overridden-binding-2.stderr b/tests/ui/associated-types/associated-types-overridden-binding-2.stderr index e96a2446b6ce..1594e5729781 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding-2.stderr +++ b/tests/ui/associated-types/associated-types-overridden-binding-2.stderr @@ -1,4 +1,4 @@ -error: conflicting associated type bounds for `Item` +error: conflicting associated type bindings for `Item` --> $DIR/associated-types-overridden-binding-2.rs:6:13 | LL | trait I32Iterator = Iterator; diff --git a/tests/ui/associated-types/associated-types-overridden-binding.rs b/tests/ui/associated-types/associated-types-overridden-binding.rs index 333a3e30c7dc..82004f042ea1 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding.rs +++ b/tests/ui/associated-types/associated-types-overridden-binding.rs @@ -8,5 +8,5 @@ trait U32Iterator = I32Iterator; //~ ERROR type annotations needed fn main() { let _: &dyn I32Iterator; - //~^ ERROR conflicting associated type bounds + //~^ ERROR conflicting associated type bindings } diff --git a/tests/ui/associated-types/associated-types-overridden-binding.stderr b/tests/ui/associated-types/associated-types-overridden-binding.stderr index 08ab9b63ee9f..d31b05065719 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding.stderr +++ b/tests/ui/associated-types/associated-types-overridden-binding.stderr @@ -22,7 +22,7 @@ note: required by a bound in `I32Iterator` LL | trait I32Iterator = Iterator; | ^^^^^^^^^^ required by this bound in `I32Iterator` -error: conflicting associated type bounds for `Item` +error: conflicting associated type bindings for `Item` --> $DIR/associated-types-overridden-binding.rs:10:13 | LL | trait I32Iterator = Iterator; From baba337869f2319234614ac01d091fb9d713b93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 6 Jan 2026 23:40:21 +0100 Subject: [PATCH 157/273] Introduce `AssocTag::descr` & refactor in the vicinity --- .../src/check/compare_impl_item.rs | 2 +- .../src/hir_ty_lowering/errors.rs | 26 +++---- .../src/hir_ty_lowering/mod.rs | 2 +- compiler/rustc_middle/src/ty/assoc.rs | 77 ++++++++++--------- src/librustdoc/clean/inline.rs | 2 +- src/tools/clippy/clippy_utils/src/ty/mod.rs | 2 +- .../elided-lifetime.rs | 2 +- .../elided-lifetime.stderr | 6 +- .../static-trait-impl.rs | 2 +- .../static-trait-impl.stderr | 6 +- .../assoc-const-missing-type.rs | 2 +- .../assoc-const-missing-type.stderr | 6 +- .../generic-const-items/compare-impl-item.rs | 10 +-- .../compare-impl-item.stderr | 14 ++-- 14 files changed, 81 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index b09e03941725..4534cfcf962e 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -1960,7 +1960,7 @@ fn compare_generic_param_kinds<'tcx>( trait_item: ty::AssocItem, delay: bool, ) -> Result<(), ErrorGuaranteed> { - assert_eq!(impl_item.as_tag(), trait_item.as_tag()); + assert_eq!(impl_item.tag(), trait_item.tag()); let ty_const_params_of = |def_id| { tcx.generics_of(def_id).own_params.iter().filter(|param| { diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index f467780143ef..3a77bad3ff52 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -171,7 +171,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let all_candidate_names: Vec<_> = all_candidates() .flat_map(|r| tcx.associated_items(r.def_id()).in_definition_order()) .filter_map(|item| { - if !item.is_impl_trait_in_trait() && item.as_tag() == assoc_tag { + if !item.is_impl_trait_in_trait() && item.tag() == assoc_tag { item.opt_name() } else { None @@ -207,7 +207,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .iter() .flat_map(|trait_def_id| tcx.associated_items(*trait_def_id).in_definition_order()) .filter_map(|item| { - (!item.is_impl_trait_in_trait() && item.as_tag() == assoc_tag).then(|| item.name()) + (!item.is_impl_trait_in_trait() && item.tag() == assoc_tag).then(|| item.name()) }) .collect(); @@ -220,7 +220,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .filter(|trait_def_id| { tcx.associated_items(trait_def_id) .filter_by_name_unhygienic(suggested_name) - .any(|item| item.as_tag() == assoc_tag) + .any(|item| item.tag() == assoc_tag) }) .collect::>()[..] { @@ -383,9 +383,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { hir::Term::Ty(ty) => ty.span, hir::Term::Const(ct) => ct.span, }; - (span, Some(ident.span), assoc_item.as_tag(), assoc_tag) + (span, Some(ident.span), assoc_item.tag(), assoc_tag) } else { - (ident.span, None, assoc_tag, assoc_item.as_tag()) + (ident.span, None, assoc_tag, assoc_item.tag()) }; self.dcx().emit_err(errors::AssocKindMismatch { @@ -393,7 +393,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { expected: assoc_tag_str(expected), got: assoc_tag_str(got), expected_because_label, - assoc_kind: assoc_tag_str(assoc_item.as_tag()), + assoc_kind: assoc_tag_str(assoc_item.tag()), def_span: tcx.def_span(assoc_item.def_id), bound_on_assoc_const_label, wrap_in_braces_sugg, @@ -965,8 +965,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { names_len += 1; descr = match descr { - None => Some(Descr::Tag(assoc_item.as_tag())), - Some(Descr::Tag(tag)) if tag != assoc_item.as_tag() => Some(Descr::Item), + None => Some(Descr::Tag(assoc_item.tag())), + Some(Descr::Tag(tag)) if tag != assoc_item.tag() => Some(Descr::Item), _ => continue, }; } @@ -1030,10 +1030,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let names = names.join(", "); let descr = match descr.unwrap() { - // FIXME(fmease): Create `ty::AssocTag::descr`. - Descr::Tag(ty::AssocTag::Type) => "associated type", - Descr::Tag(ty::AssocTag::Const) => "associated constant", - _ => "associated item", + Descr::Item => "associated item", + Descr::Tag(tag) => tag.descr(), }; let mut err = struct_span_code_err!( self.dcx(), @@ -1050,13 +1048,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let mut names: UnordMap<_, usize> = Default::default(); for (item, _) in &missing_assoc_items { items_count += 1; - *names.entry((item.name(), item.as_tag())).or_insert(0) += 1; + *names.entry((item.name(), item.tag())).or_insert(0) += 1; } let mut dupes = false; let mut shadows = false; for (item, trait_ref) in &missing_assoc_items { let name = item.name(); - let key = (name, item.as_tag()); + let key = (name, item.tag()); if names[&key] > 1 { dupes = true; diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 924967b65c19..867c588e302d 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -1768,7 +1768,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let item = tcx .associated_items(scope) .filter_by_name_unhygienic(ident.name) - .find(|i| i.as_tag() == assoc_tag && i.ident(tcx).normalize_to_macros_2_0() == ident)?; + .find(|i| i.tag() == assoc_tag && i.ident(tcx).normalize_to_macros_2_0() == ident)?; Some((*item, def_scope)) } diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 4a54a3109462..8c560df4e162 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -126,27 +126,15 @@ impl AssocItem { } pub fn descr(&self) -> &'static str { - match self.kind { - ty::AssocKind::Const { .. } => "associated const", - ty::AssocKind::Fn { has_self: true, .. } => "method", - ty::AssocKind::Fn { has_self: false, .. } => "associated function", - ty::AssocKind::Type { .. } => "associated type", - } + self.kind.descr() } pub fn namespace(&self) -> Namespace { - match self.kind { - ty::AssocKind::Type { .. } => Namespace::TypeNS, - ty::AssocKind::Const { .. } | ty::AssocKind::Fn { .. } => Namespace::ValueNS, - } + self.kind.namespace() } pub fn as_def_kind(&self) -> DefKind { - match self.kind { - AssocKind::Const { .. } => DefKind::AssocConst, - AssocKind::Fn { .. } => DefKind::AssocFn, - AssocKind::Type { .. } => DefKind::AssocTy, - } + self.kind.as_def_kind() } pub fn is_const(&self) -> bool { @@ -165,12 +153,8 @@ impl AssocItem { matches!(self.kind, ty::AssocKind::Type { .. }) } - pub fn as_tag(&self) -> AssocTag { - match self.kind { - AssocKind::Const { .. } => AssocTag::Const, - AssocKind::Fn { .. } => AssocTag::Fn, - AssocKind::Type { .. } => AssocTag::Type, - } + pub fn tag(&self) -> AssocTag { + self.kind.tag() } pub fn is_impl_trait_in_trait(&self) -> bool { @@ -196,33 +180,43 @@ pub enum AssocKind { impl AssocKind { pub fn namespace(&self) -> Namespace { - match *self { - ty::AssocKind::Type { .. } => Namespace::TypeNS, - ty::AssocKind::Const { .. } | ty::AssocKind::Fn { .. } => Namespace::ValueNS, + match self { + Self::Type { .. } => Namespace::TypeNS, + Self::Const { .. } | Self::Fn { .. } => Namespace::ValueNS, + } + } + + pub fn tag(&self) -> AssocTag { + match self { + Self::Const { .. } => AssocTag::Const, + Self::Fn { .. } => AssocTag::Fn, + Self::Type { .. } => AssocTag::Type, } } pub fn as_def_kind(&self) -> DefKind { match self { - AssocKind::Const { .. } => DefKind::AssocConst, - AssocKind::Fn { .. } => DefKind::AssocFn, - AssocKind::Type { .. } => DefKind::AssocTy, + Self::Const { .. } => DefKind::AssocConst, + Self::Fn { .. } => DefKind::AssocFn, + Self::Type { .. } => DefKind::AssocTy, + } + } + + pub fn descr(&self) -> &'static str { + match self { + Self::Fn { has_self: true, .. } => "method", + _ => self.tag().descr(), } } } impl std::fmt::Display for AssocKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AssocKind::Fn { has_self: true, .. } => write!(f, "method"), - AssocKind::Fn { has_self: false, .. } => write!(f, "associated function"), - AssocKind::Const { .. } => write!(f, "associated const"), - AssocKind::Type { .. } => write!(f, "associated type"), - } + f.write_str(self.descr()) } } -// Like `AssocKind`, but just the tag, no fields. Used in various kinds of matching. +/// Like [`AssocKind`], but just the tag, no fields. Used in various kinds of matching. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AssocTag { Const, @@ -230,6 +224,17 @@ pub enum AssocTag { Type, } +impl AssocTag { + pub fn descr(self) -> &'static str { + // This should match `DefKind::descr`. + match self { + Self::Const => "associated constant", + Self::Fn => "associated function", + Self::Type => "associated type", + } + } +} + /// A list of `ty::AssocItem`s in definition order that allows for efficient lookup by name. /// /// When doing lookup by name, we try to postpone hygienic comparison for as long as possible since @@ -277,7 +282,7 @@ impl AssocItems { name: Symbol, assoc_tag: AssocTag, ) -> impl '_ + Iterator { - self.filter_by_name_unhygienic(name).filter(move |item| item.as_tag() == assoc_tag) + self.filter_by_name_unhygienic(name).filter(move |item| item.tag() == assoc_tag) } /// Returns the associated item with the given identifier and `AssocKind`, if one exists. @@ -290,7 +295,7 @@ impl AssocItems { parent_def_id: DefId, ) -> Option<&ty::AssocItem> { self.filter_by_name_unhygienic(ident.name) - .filter(|item| item.as_tag() == assoc_tag) + .filter(|item| item.tag() == assoc_tag) .find(|item| tcx.hygienic_eq(ident, item.ident(tcx), parent_def_id)) } diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 19974f4847d4..22737cda97e5 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -561,7 +561,7 @@ pub(crate) fn build_impl( .find_by_ident_and_kind( tcx, item.ident(tcx), - item.as_tag(), + item.tag(), associated_trait.def_id, ) .unwrap(); // corresponding associated item has to exist diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs index a90d64e972c1..c1be4acc7068 100644 --- a/src/tools/clippy/clippy_utils/src/ty/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs @@ -1226,7 +1226,7 @@ pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_n .associated_items(did) .filter_by_name_unhygienic(method_name) .next() - .filter(|item| item.as_tag() == AssocTag::Fn) + .filter(|item| item.tag() == AssocTag::Fn) }) } else { None diff --git a/tests/ui/consts/static-default-lifetime/elided-lifetime.rs b/tests/ui/consts/static-default-lifetime/elided-lifetime.rs index d60fe7d409af..989de389180a 100644 --- a/tests/ui/consts/static-default-lifetime/elided-lifetime.rs +++ b/tests/ui/consts/static-default-lifetime/elided-lifetime.rs @@ -16,7 +16,7 @@ impl Bar for Foo<'_> { const STATIC: &str = ""; //~^ ERROR `&` without an explicit lifetime name cannot be used here //~| WARN this was previously accepted by the compiler but is being phased out - //~| ERROR lifetime parameters or bounds on associated const `STATIC` do not match the trait declaration + //~| ERROR lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration } fn main() {} diff --git a/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr b/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr index bb8365b0ae56..ae4a48e4e932 100644 --- a/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr +++ b/tests/ui/consts/static-default-lifetime/elided-lifetime.stderr @@ -39,14 +39,14 @@ help: use the `'static` lifetime LL | const STATIC: &'static str = ""; | +++++++ -error[E0195]: lifetime parameters or bounds on associated const `STATIC` do not match the trait declaration +error[E0195]: lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration --> $DIR/elided-lifetime.rs:16:17 | LL | const STATIC: &str; - | - lifetimes in impl do not match this associated const in trait + | - lifetimes in impl do not match this associated constant in trait ... LL | const STATIC: &str = ""; - | ^ lifetimes do not match associated const in trait + | ^ lifetimes do not match associated constant in trait error: aborting due to 3 previous errors diff --git a/tests/ui/consts/static-default-lifetime/static-trait-impl.rs b/tests/ui/consts/static-default-lifetime/static-trait-impl.rs index 85746df146fc..ecc163aecbf1 100644 --- a/tests/ui/consts/static-default-lifetime/static-trait-impl.rs +++ b/tests/ui/consts/static-default-lifetime/static-trait-impl.rs @@ -9,7 +9,7 @@ impl Bar<'_> for A { const STATIC: &str = ""; //~^ ERROR `&` without an explicit lifetime name cannot be used here //~| WARN this was previously accepted by the compiler but is being phased out - //~| ERROR lifetime parameters or bounds on associated const `STATIC` do not match the trait declaration + //~| ERROR lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration } struct B; diff --git a/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr b/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr index 38d24db1317f..2bc271dccad9 100644 --- a/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr +++ b/tests/ui/consts/static-default-lifetime/static-trait-impl.stderr @@ -21,14 +21,14 @@ help: use the `'static` lifetime LL | const STATIC: &'static str = ""; | +++++++ -error[E0195]: lifetime parameters or bounds on associated const `STATIC` do not match the trait declaration +error[E0195]: lifetime parameters or bounds on associated constant `STATIC` do not match the trait declaration --> $DIR/static-trait-impl.rs:9:17 | LL | const STATIC: &'a str; - | - lifetimes in impl do not match this associated const in trait + | - lifetimes in impl do not match this associated constant in trait ... LL | const STATIC: &str = ""; - | ^ lifetimes do not match associated const in trait + | ^ lifetimes do not match associated constant in trait error: aborting due to 2 previous errors diff --git a/tests/ui/generic-const-items/assoc-const-missing-type.rs b/tests/ui/generic-const-items/assoc-const-missing-type.rs index dde47cf993ea..2b7167ad067e 100644 --- a/tests/ui/generic-const-items/assoc-const-missing-type.rs +++ b/tests/ui/generic-const-items/assoc-const-missing-type.rs @@ -14,7 +14,7 @@ impl Trait for () { //~| ERROR mismatched types const Q = ""; //~^ ERROR missing type for `const` item - //~| ERROR lifetime parameters or bounds on associated const `Q` do not match the trait declaration + //~| ERROR lifetime parameters or bounds on associated constant `Q` do not match the trait declaration } fn main() {} diff --git a/tests/ui/generic-const-items/assoc-const-missing-type.stderr b/tests/ui/generic-const-items/assoc-const-missing-type.stderr index 9f6db575ec23..7c79133d64ec 100644 --- a/tests/ui/generic-const-items/assoc-const-missing-type.stderr +++ b/tests/ui/generic-const-items/assoc-const-missing-type.stderr @@ -15,14 +15,14 @@ error: missing type for `const` item LL | const K = (); | ^ help: provide a type for the associated constant: `()` -error[E0195]: lifetime parameters or bounds on associated const `Q` do not match the trait declaration +error[E0195]: lifetime parameters or bounds on associated constant `Q` do not match the trait declaration --> $DIR/assoc-const-missing-type.rs:15:12 | LL | const Q<'a>: &'a str; - | ---- lifetimes in impl do not match this associated const in trait + | ---- lifetimes in impl do not match this associated constant in trait ... LL | const Q = ""; - | ^ lifetimes do not match associated const in trait + | ^ lifetimes do not match associated constant in trait error: missing type for `const` item --> $DIR/assoc-const-missing-type.rs:15:12 diff --git a/tests/ui/generic-const-items/compare-impl-item.rs b/tests/ui/generic-const-items/compare-impl-item.rs index b301cd0dae0c..e8a23b5c44bb 100644 --- a/tests/ui/generic-const-items/compare-impl-item.rs +++ b/tests/ui/generic-const-items/compare-impl-item.rs @@ -14,15 +14,15 @@ trait Trait

{ impl

Trait

for () { const A: () = (); - //~^ ERROR const `A` has 1 type parameter but its trait declaration has 0 type parameters + //~^ ERROR constant `A` has 1 type parameter but its trait declaration has 0 type parameters const B: u64 = 0; - //~^ ERROR const `B` has 1 const parameter but its trait declaration has 2 const parameters + //~^ ERROR constant `B` has 1 const parameter but its trait declaration has 2 const parameters const C<'a>: &'a str = ""; - //~^ ERROR const `C` has 0 type parameters but its trait declaration has 1 type parameter + //~^ ERROR constant `C` has 0 type parameters but its trait declaration has 1 type parameter const D: u16 = N; - //~^ ERROR const `D` has an incompatible generic parameter for trait `Trait` + //~^ ERROR constant `D` has an incompatible generic parameter for trait `Trait` const E: &'static () = &(); - //~^ ERROR lifetime parameters or bounds on associated const `E` do not match the trait declaration + //~^ ERROR lifetime parameters or bounds on associated constant `E` do not match the trait declaration const F: usize = 1024 where diff --git a/tests/ui/generic-const-items/compare-impl-item.stderr b/tests/ui/generic-const-items/compare-impl-item.stderr index f7e3ff6501b1..ffe65f9fc088 100644 --- a/tests/ui/generic-const-items/compare-impl-item.stderr +++ b/tests/ui/generic-const-items/compare-impl-item.stderr @@ -1,4 +1,4 @@ -error[E0049]: associated const `A` has 1 type parameter but its trait declaration has 0 type parameters +error[E0049]: associated constant `A` has 1 type parameter but its trait declaration has 0 type parameters --> $DIR/compare-impl-item.rs:16:13 | LL | const A: (); @@ -7,7 +7,7 @@ LL | const A: (); LL | const A: () = (); | ^ found 1 type parameter -error[E0049]: associated const `B` has 1 const parameter but its trait declaration has 2 const parameters +error[E0049]: associated constant `B` has 1 const parameter but its trait declaration has 2 const parameters --> $DIR/compare-impl-item.rs:18:13 | LL | const B: u64; @@ -18,7 +18,7 @@ LL | const B: u64; LL | const B: u64 = 0; | ^^^^^^^^^^^^ found 1 const parameter -error[E0049]: associated const `C` has 0 type parameters but its trait declaration has 1 type parameter +error[E0049]: associated constant `C` has 0 type parameters but its trait declaration has 1 type parameter --> $DIR/compare-impl-item.rs:20:13 | LL | const C: T; @@ -27,7 +27,7 @@ LL | const C: T; LL | const C<'a>: &'a str = ""; | ^^ found 0 type parameters -error[E0053]: associated const `D` has an incompatible generic parameter for trait `Trait` +error[E0053]: associated constant `D` has an incompatible generic parameter for trait `Trait` --> $DIR/compare-impl-item.rs:22:13 | LL | trait Trait

{ @@ -42,14 +42,14 @@ LL | impl

Trait

for () { LL | const D: u16 = N; | ^^^^^^^^^^^^ found const parameter of type `u16` -error[E0195]: lifetime parameters or bounds on associated const `E` do not match the trait declaration +error[E0195]: lifetime parameters or bounds on associated constant `E` do not match the trait declaration --> $DIR/compare-impl-item.rs:24:12 | LL | const E<'a>: &'a (); - | ---- lifetimes in impl do not match this associated const in trait + | ---- lifetimes in impl do not match this associated constant in trait ... LL | const E: &'static () = &(); - | ^ lifetimes do not match associated const in trait + | ^ lifetimes do not match associated constant in trait error[E0276]: impl has stricter requirements than trait --> $DIR/compare-impl-item.rs:29:12 From 233a45c41ad689dfb7cd6249dcb4e7e3eda01971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 7 Jan 2026 16:32:14 +0100 Subject: [PATCH 158/273] Fix handling of const params defaults that ref `Self` & generalize diag We used to lower such bad defaulted const args in trait object types to `{type error}`; now correctly lower them to `{const error}`. The added tests used to ICE prior to this change. --- compiler/rustc_hir_analysis/messages.ftl | 53 +++++++-------- compiler/rustc_hir_analysis/src/errors.rs | 67 +++++++++++++------ .../src/hir_ty_lowering/dyn_trait.rs | 12 ++-- .../src/hir_ty_lowering/errors.rs | 16 ++--- tests/crashes/136063.rs | 6 -- tests/crashes/137260.rs | 11 --- tests/crashes/137514.rs | 9 --- ...ompat-const-param-default-mentions-self.rs | 21 ++++++ ...t-const-param-default-mentions-self.stderr | 18 +++++ .../default-param-self-projection.stderr | 8 +-- tests/ui/error-codes/E0393.stderr | 8 +-- tests/ui/issues/issue-22370.stderr | 8 +-- .../unspecified-self-in-trait-ref.stderr | 2 +- ...parameter-defaults-referencing-Self.stderr | 8 +-- 14 files changed, 139 insertions(+), 108 deletions(-) delete mode 100644 tests/crashes/136063.rs delete mode 100644 tests/crashes/137260.rs delete mode 100644 tests/crashes/137514.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.stderr diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index fa3c4cb05f96..9942cddcb3cd 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -330,6 +330,31 @@ hir_analysis_manual_implementation = hir_analysis_method_should_return_future = method should be `async` or return a future, but it is synchronous .note = this method is `async` so it expects a future to be returned +hir_analysis_missing_generic_params = + the {$descr} {$parameterCount -> + [one] parameter + *[other] parameters + } {$parameters} must be explicitly specified + .label = {$descr} {$parameterCount -> + [one] parameter + *[other] parameters + } {$parameters} must be specified for this + .suggestion = explicitly specify the {$descr} {$parameterCount -> + [one] parameter + *[other] parameters + } + .no_suggestion_label = missing {$parameterCount -> + [one] reference + *[other] references + } to {$parameters} + .note = because the parameter {$parameterCount -> + [one] default references + *[other] defaults reference + } `Self`, the {$parameterCount -> + [one] parameter + *[other] parameters + } must be specified on the trait object type + hir_analysis_missing_one_of_trait_item = not all trait items implemented, missing one of: `{$missing_items_msg}` .label = missing one of `{$missing_items_msg}` in implementation .note = required because of this annotation @@ -346,34 +371,6 @@ hir_analysis_missing_trait_item_unstable = not all trait items implemented, miss .some_note = use of unstable library feature `{$feature}`: {$reason} .none_note = use of unstable library feature `{$feature}` -hir_analysis_missing_type_params = - the type {$parameterCount -> - [one] parameter - *[other] parameters - } {$parameters} must be explicitly specified - .label = type {$parameterCount -> - [one] parameter - *[other] parameters - } {$parameters} must be specified for this - .suggestion = set the type {$parameterCount -> - [one] parameter - *[other] parameters - } to the desired {$parameterCount -> - [one] type - *[other] types - } - .no_suggestion_label = missing {$parameterCount -> - [one] reference - *[other] references - } to {$parameters} - .note = because the parameter {$parameterCount -> - [one] default references - *[other] defaults reference - } `Self`, the {$parameterCount -> - [one] parameter - *[other] parameters - } must be specified on the object type - hir_analysis_no_variant_named = no variant named `{$ident}` found for enum `{$ty}` hir_analysis_not_supported_delegation = {$descr} diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 185a822bdfa6..e513f053d336 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -4,11 +4,11 @@ use rustc_abi::ExternAbi; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, - MultiSpan, + MultiSpan, listify, }; use rustc_hir::limit::Limit; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; use rustc_span::{Ident, Span, Symbol}; use crate::fluent_generated as fluent; @@ -400,35 +400,58 @@ pub(crate) struct UnconstrainedOpaqueType { pub what: &'static str, } -pub(crate) struct MissingTypeParams { +pub(crate) struct MissingGenericParams { pub span: Span, pub def_span: Span, pub span_snippet: Option, - pub missing_type_params: Vec, + pub missing_generic_params: Vec<(Symbol, ty::GenericParamDefKind)>, pub empty_generic_args: bool, } -// Manual implementation of `Diagnostic` to be able to call `span_to_snippet`. -impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for MissingTypeParams { +// FIXME: This doesn't need to be a manual impl! +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for MissingGenericParams { #[track_caller] fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { - let mut err = Diag::new(dcx, level, fluent::hir_analysis_missing_type_params); + let mut err = Diag::new(dcx, level, fluent::hir_analysis_missing_generic_params); err.span(self.span); err.code(E0393); - err.arg("parameterCount", self.missing_type_params.len()); - err.arg( - "parameters", - self.missing_type_params - .iter() - .map(|n| format!("`{n}`")) - .collect::>() - .join(", "), - ); - err.span_label(self.def_span, fluent::hir_analysis_label); + enum Descr { + Generic, + Type, + Const, + } + + let mut descr = None; + for (_, kind) in &self.missing_generic_params { + descr = match (&descr, kind) { + (None, ty::GenericParamDefKind::Type { .. }) => Some(Descr::Type), + (None, ty::GenericParamDefKind::Const { .. }) => Some(Descr::Const), + (Some(Descr::Type), ty::GenericParamDefKind::Const { .. }) + | (Some(Descr::Const), ty::GenericParamDefKind::Type { .. }) => { + Some(Descr::Generic) + } + _ => continue, + } + } + + err.arg( + "descr", + match descr.unwrap() { + Descr::Generic => "generic", + Descr::Type => "type", + Descr::Const => "const", + }, + ); + err.arg("parameterCount", self.missing_generic_params.len()); + err.arg( + "parameters", + listify(&self.missing_generic_params, |(n, _)| format!("`{n}`")).unwrap(), + ); + let mut suggested = false; - // Don't suggest setting the type params if there are some already: the order is + // Don't suggest setting the generic params if there are some already: The order is // tricky to get right and the user will already know what the syntax is. if let Some(snippet) = self.span_snippet && self.empty_generic_args @@ -438,16 +461,16 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for MissingTypeParams { // we would have to preserve the right order. For now, as clearly the user is // aware of the syntax, we do nothing. } else { - // The user wrote `Iterator`, so we don't have a type we can suggest, but at - // least we can clue them to the correct syntax `Iterator`. + // The user wrote `Trait`, so we don't have a type we can suggest, but at + // least we can clue them to the correct syntax `Trait`. err.span_suggestion_verbose( self.span.shrink_to_hi(), fluent::hir_analysis_suggestion, format!( "<{}>", - self.missing_type_params + self.missing_generic_params .iter() - .map(|n| n.to_string()) + .map(|(n, _)| format!("/* {n} */")) .collect::>() .join(", ") ), diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index f4002396b9d8..3b4812bc9469 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -352,9 +352,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let span = *spans.first().unwrap(); - // Verify that `dummy_self` did not leak inside default type parameters. This + // Verify that `dummy_self` did not leak inside generic parameter defaults. This // could not be done at path creation, since we need to see through trait aliases. - let mut missing_type_params = vec![]; + let mut missing_generic_params = Vec::new(); let generics = tcx.generics_of(trait_ref.def_id); let args: Vec<_> = trait_ref .args @@ -365,8 +365,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .map(|(index, arg)| { if arg.walk().any(|arg| arg == dummy_self.into()) { let param = &generics.own_params[index]; - missing_type_params.push(param.name); - Ty::new_misc_error(tcx).into() + missing_generic_params.push((param.name, param.kind.clone())); + param.to_error(tcx) } else { arg } @@ -377,8 +377,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { hir_bound.trait_ref.path.res == Res::Def(DefKind::Trait, trait_ref.def_id) && hir_bound.span.contains(span) }); - self.report_missing_type_params( - missing_type_params, + self.report_missing_generic_params( + missing_generic_params, trait_ref.def_id, span, empty_generic_args, diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 3a77bad3ff52..d114691b25e8 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -28,31 +28,29 @@ use tracing::debug; use super::InherentAssocCandidate; use crate::errors::{ - self, AssocItemConstraintsNotAllowedHere, ManualImplementation, MissingTypeParams, - ParenthesizedFnTraitExpansion, TraitObjectDeclaredWithNoTraits, + self, AssocItemConstraintsNotAllowedHere, ManualImplementation, ParenthesizedFnTraitExpansion, + TraitObjectDeclaredWithNoTraits, }; use crate::fluent_generated as fluent; use crate::hir_ty_lowering::{AssocItemQSelf, HirTyLowerer}; impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { - /// On missing type parameters, emit an E0393 error and provide a structured suggestion using - /// the type parameter's name as a placeholder. - pub(crate) fn report_missing_type_params( + pub(crate) fn report_missing_generic_params( &self, - missing_type_params: Vec, + missing_generic_params: Vec<(Symbol, ty::GenericParamDefKind)>, def_id: DefId, span: Span, empty_generic_args: bool, ) { - if missing_type_params.is_empty() { + if missing_generic_params.is_empty() { return; } - self.dcx().emit_err(MissingTypeParams { + self.dcx().emit_err(errors::MissingGenericParams { span, def_span: self.tcx().def_span(def_id), span_snippet: self.tcx().sess.source_map().span_to_snippet(span).ok(), - missing_type_params, + missing_generic_params, empty_generic_args, }); } diff --git a/tests/crashes/136063.rs b/tests/crashes/136063.rs deleted file mode 100644 index 078cc59dfa28..000000000000 --- a/tests/crashes/136063.rs +++ /dev/null @@ -1,6 +0,0 @@ -//@ known-bug: #136063 -#![feature(generic_const_exprs)] -trait A {} -impl A<1> for bool {} -fn bar(arg : &dyn A) { bar(true) } -pub fn main() {} diff --git a/tests/crashes/137260.rs b/tests/crashes/137260.rs deleted file mode 100644 index f1fa8a660dcd..000000000000 --- a/tests/crashes/137260.rs +++ /dev/null @@ -1,11 +0,0 @@ -//@ known-bug: #137260 -#![feature(generic_const_exprs)] -#![allow(incomplete_features)] - -trait Iter {} - -fn needs_iter>() {} - -fn test() { - needs_iter::<1, dyn Iter<()>>(); -} diff --git a/tests/crashes/137514.rs b/tests/crashes/137514.rs deleted file mode 100644 index 7ae5f29e36e6..000000000000 --- a/tests/crashes/137514.rs +++ /dev/null @@ -1,9 +0,0 @@ -//@ known-bug: #137514 -//@ needs-rustc-debug-assertions -#![feature(generic_const_exprs)] - -trait Bar {} - -trait BB = Bar<{ 1i32 + 1 }>; - -fn foo(x: &dyn BB) {} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.rs new file mode 100644 index 000000000000..30e7a78c2a85 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.rs @@ -0,0 +1,21 @@ +// Test that we force users to explicitly specify const arguments for const parameters that +// have defaults if the default mentions the `Self` type parameter. + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait X::N }> {} + +trait Y { + #[type_const] + const N: usize; +} + +impl Y for T { + #[type_const] + const N: usize = 1; +} + +fn main() { + let _: dyn X; //~ ERROR the const parameter `N` must be explicitly specified +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.stderr new file mode 100644 index 000000000000..d92fd620f0ea --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-param-default-mentions-self.stderr @@ -0,0 +1,18 @@ +error[E0393]: the const parameter `N` must be explicitly specified + --> $DIR/dyn-compat-const-param-default-mentions-self.rs:20:16 + | +LL | trait X::N }> {} + | -------------------------------------------- const parameter `N` must be specified for this +... +LL | let _: dyn X; + | ^ + | + = note: because the parameter default references `Self`, the parameter must be specified on the trait object type +help: explicitly specify the const parameter + | +LL | let _: dyn X; + | +++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0393`. diff --git a/tests/ui/dyn-compatibility/default-param-self-projection.stderr b/tests/ui/dyn-compatibility/default-param-self-projection.stderr index ea252a99b048..86f85f4da8e6 100644 --- a/tests/ui/dyn-compatibility/default-param-self-projection.stderr +++ b/tests/ui/dyn-compatibility/default-param-self-projection.stderr @@ -7,11 +7,11 @@ LL | trait A::E> {} LL | let B: &dyn A = &(); | ^ | - = note: because the parameter default references `Self`, the parameter must be specified on the object type -help: set the type parameter to the desired type + = note: because the parameter default references `Self`, the parameter must be specified on the trait object type +help: explicitly specify the type parameter | -LL | let B: &dyn A = &(); - | +++ +LL | let B: &dyn A = &(); + | +++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/error-codes/E0393.stderr b/tests/ui/error-codes/E0393.stderr index 4847aa2508da..a6991aa355fa 100644 --- a/tests/ui/error-codes/E0393.stderr +++ b/tests/ui/error-codes/E0393.stderr @@ -7,11 +7,11 @@ LL | LL | fn together_we_will_rule_the_galaxy(son: &dyn A) {} | ^ | - = note: because the parameter default references `Self`, the parameter must be specified on the object type -help: set the type parameter to the desired type + = note: because the parameter default references `Self`, the parameter must be specified on the trait object type +help: explicitly specify the type parameter | -LL | fn together_we_will_rule_the_galaxy(son: &dyn A) {} - | +++ +LL | fn together_we_will_rule_the_galaxy(son: &dyn A) {} + | +++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/issues/issue-22370.stderr b/tests/ui/issues/issue-22370.stderr index b02d867eb7dd..cd1580e844ca 100644 --- a/tests/ui/issues/issue-22370.stderr +++ b/tests/ui/issues/issue-22370.stderr @@ -7,11 +7,11 @@ LL | LL | fn f(a: &dyn A) {} | ^ | - = note: because the parameter default references `Self`, the parameter must be specified on the object type -help: set the type parameter to the desired type + = note: because the parameter default references `Self`, the parameter must be specified on the trait object type +help: explicitly specify the type parameter | -LL | fn f(a: &dyn A) {} - | +++ +LL | fn f(a: &dyn A) {} + | +++++++++ error: aborting due to 1 previous error diff --git a/tests/ui/traits/unspecified-self-in-trait-ref.stderr b/tests/ui/traits/unspecified-self-in-trait-ref.stderr index 86b77193155d..b7d78a3284e3 100644 --- a/tests/ui/traits/unspecified-self-in-trait-ref.stderr +++ b/tests/ui/traits/unspecified-self-in-trait-ref.stderr @@ -97,7 +97,7 @@ LL | pub trait Bar { LL | let e = Bar::::lol(); | ^^^^^^^^^^^^ missing reference to `A` | - = note: because the parameter default references `Self`, the parameter must be specified on the object type + = note: because the parameter default references `Self`, the parameter must be specified on the trait object type error: aborting due to 5 previous errors; 5 warnings emitted diff --git a/tests/ui/type/type-parameter-defaults-referencing-Self.stderr b/tests/ui/type/type-parameter-defaults-referencing-Self.stderr index 23f10c9262c7..300ad1998e42 100644 --- a/tests/ui/type/type-parameter-defaults-referencing-Self.stderr +++ b/tests/ui/type/type-parameter-defaults-referencing-Self.stderr @@ -7,11 +7,11 @@ LL | trait Foo { LL | fn foo(x: &dyn Foo) { } | ^^^ | - = note: because the parameter default references `Self`, the parameter must be specified on the object type -help: set the type parameter to the desired type + = note: because the parameter default references `Self`, the parameter must be specified on the trait object type +help: explicitly specify the type parameter | -LL | fn foo(x: &dyn Foo) { } - | +++ +LL | fn foo(x: &dyn Foo) { } + | +++++++++ error: aborting due to 1 previous error From eb0e3ac68785f171dcc4ff3d7ecc3290bd93ebb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 7 Jan 2026 18:02:47 +0100 Subject: [PATCH 159/273] mGCA: Force users to specify type assoc consts from supertraits that would otherwise ref `Self` The added test used to ICE prior to this change. --- .../src/hir_ty_lowering/dyn_trait.rs | 7 ++----- ...rojection-from-supertrait-mentions-self.rs | 20 +++++++++++++++++++ ...ction-from-supertrait-mentions-self.stderr | 12 +++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index 3b4812bc9469..40a23a1f51f1 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -236,11 +236,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let pred = bound_predicate.rebind(pred); // A `Self` within the original bound will be instantiated with a // `trait_object_dummy_self`, so check for that. - let references_self = match pred.skip_binder().term.kind() { - ty::TermKind::Ty(ty) => ty.walk().any(|arg| arg == dummy_self.into()), - // FIXME(mgca): We should walk the const instead of not doing anything - ty::TermKind::Const(_) => false, - }; + let references_self = + pred.skip_binder().term.walk().any(|arg| arg == dummy_self.into()); // If the projection output contains `Self`, force the user to // elaborate it explicitly to avoid a lot of complexity. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.rs new file mode 100644 index 000000000000..aa93edd02531 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.rs @@ -0,0 +1,20 @@ +// Test that we force users to explicitly specify associated constants (via bindings) +// which reference the `Self` type parameter. + +#![feature(min_generic_const_args)] +#![expect(incomplete_features)] + +trait X: Y { + #[type_const] + const Q: usize; +} + +trait Y { + #[type_const] + const K: usize; +} + +fn main() { + let _: dyn X; + //~^ ERROR the value of the associated constant `K` in `Y` must be specified +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.stderr new file mode 100644 index 000000000000..373c4f0e6611 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-from-supertrait-mentions-self.stderr @@ -0,0 +1,12 @@ +error[E0191]: the value of the associated constant `K` in `Y` must be specified + --> $DIR/dyn-compat-const-projection-from-supertrait-mentions-self.rs:18:16 + | +LL | const K: usize; + | -------------- `K` defined here +... +LL | let _: dyn X; + | ^^^^^^^^^ help: specify the associated constant: `X` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0191`. From 618c15eb6ce195c8ec19f4501addb728eeb81a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 11 Jan 2026 18:16:45 +0100 Subject: [PATCH 160/273] Reject const projections behind trait aliases that mention `Self` This fully rewords the diagnostic that was previously only emitted for assoc ty bindings. That's because it incorrectly called trait aliases *type aliases* and didn't really make it clear what the root cause is. The added test used to ICE prior to this change. I've double-checked that the preexisting test I've modified still ICEs in nightly-2025-03-29. --- compiler/rustc_hir_analysis/messages.ftl | 8 ++-- compiler/rustc_hir_analysis/src/errors.rs | 7 +++- .../src/hir_ty_lowering/dyn_trait.rs | 40 +++++++++---------- ...ection-behind-trait-alias-mentions-self.rs | 22 ++++++++++ ...on-behind-trait-alias-mentions-self.stderr | 11 +++++ .../trait-alias-self-projection.rs | 12 ------ .../trait-alias-self-projection.stderr | 9 ----- ...ection-behind-trait-alias-mentions-self.rs | 18 +++++++++ ...on-behind-trait-alias-mentions-self.stderr | 20 ++++++++++ 9 files changed, 101 insertions(+), 46 deletions(-) create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.stderr delete mode 100644 tests/ui/dyn-compatibility/trait-alias-self-projection.rs delete mode 100644 tests/ui/dyn-compatibility/trait-alias-self-projection.stderr create mode 100644 tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.rs create mode 100644 tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.stderr diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 9942cddcb3cd..d9f8eba65c4a 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -165,6 +165,11 @@ hir_analysis_drop_impl_reservation = reservation `Drop` impls are not supported hir_analysis_duplicate_precise_capture = cannot capture parameter `{$name}` twice .label = parameter captured again here +hir_analysis_dyn_trait_assoc_item_binding_mentions_self = + {$kind} binding in trait object type mentions `Self` + .label = contains a mention of `Self` + .binding_label = this binding mentions `Self` + hir_analysis_eii_with_generics = `{$impl_name}` cannot have generic parameters other than lifetimes .label = required by this attribute @@ -478,9 +483,6 @@ hir_analysis_self_in_impl_self = `Self` is not valid in the self type of an impl block .note = replace `Self` with a different type -hir_analysis_self_in_type_alias = `Self` is not allowed in type aliases - .label = `Self` is only available in impls, traits, and concrete type definitions - hir_analysis_self_ty_not_captured = `impl Trait` must mention the `Self` type of the trait in `use<...>` .label = `Self` type parameter is implicitly captured by this `impl Trait` .note = currently, all type parameters are required to be mentioned in the precise captures list diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index e513f053d336..2a77d0b997e2 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1632,11 +1632,14 @@ pub(crate) enum SupertraitItemShadowee { } #[derive(Diagnostic)] -#[diag(hir_analysis_self_in_type_alias, code = E0411)] -pub(crate) struct SelfInTypeAlias { +#[diag(hir_analysis_dyn_trait_assoc_item_binding_mentions_self)] +pub(crate) struct DynTraitAssocItemBindingMentionsSelf { #[primary_span] #[label] pub span: Span, + pub kind: &'static str, + #[label(hir_analysis_binding_label)] + pub binding: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index 40a23a1f51f1..c1cbc44953ad 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -22,7 +22,7 @@ use smallvec::{SmallVec, smallvec}; use tracing::{debug, instrument}; use super::HirTyLowerer; -use crate::errors::SelfInTypeAlias; +use crate::errors::DynTraitAssocItemBindingMentionsSelf; use crate::hir_ty_lowering::{ GenericArgCountMismatch, ImpliedBoundsContext, OverlappingAsssocItemConstraints, PredicateFilter, RegionInferReason, @@ -140,29 +140,29 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Map the projection bounds onto a key that makes it easy to remove redundant // bounds that are constrained by supertraits of the principal trait. // - // Also make sure we detect conflicting bounds from expanding a trait alias and - // also specifying it manually, like: - // ``` - // type Alias = Trait; - // let _: &dyn Alias = /* ... */; - // ``` + // Also make sure we detect conflicting bounds from expanding trait aliases. + // + // FIXME(#150936): Since the elaborated projection bounds also include the user-written ones + // and we're separately rejecting duplicate+conflicting bindings for trait + // object types when lowering assoc item bindings, there are basic cases + // where we're emitting two distinct but very similar diagnostics. let mut projection_bounds = FxIndexMap::default(); for (proj, proj_span) in elaborated_projection_bounds { - let proj = proj.map_bound(|mut b| { - if let Some(term_ty) = &b.term.as_type() { - let references_self = term_ty.walk().any(|arg| arg == dummy_self.into()); - if references_self { - // With trait alias and type alias combined, type resolver - // may not be able to catch all illegal `Self` usages (issue 139082) - let guar = self.dcx().emit_err(SelfInTypeAlias { span }); - b.term = replace_dummy_self_with_error(tcx, b.term, guar); - } - } - b - }); - let item_def_id = proj.item_def_id(); + let proj = proj.map_bound(|mut proj| { + let references_self = proj.term.walk().any(|arg| arg == dummy_self.into()); + if references_self { + let guar = self.dcx().emit_err(DynTraitAssocItemBindingMentionsSelf { + span, + kind: tcx.def_descr(item_def_id), + binding: proj_span, + }); + proj.term = replace_dummy_self_with_error(tcx, proj.term, guar); + } + proj + }); + let key = ( item_def_id, tcx.anonymize_bound_vars( diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs new file mode 100644 index 000000000000..93036fbc01a8 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs @@ -0,0 +1,22 @@ +// Check that we reject const projections behind trait aliases that mention `Self`. +// The code below is pretty artifical and contains a type mismatch anyway but we still need to +// reject it & lower the `Self` ty param to a `{type error}` to avoid ICEs down the line. +// +// The author of the trait object type can't fix this unlike the supertrait bound +// equivalent where they just need to explicitly specify the assoc const. + +#![feature(min_generic_const_args, trait_alias)] +#![expect(incomplete_features)] + +trait Trait { + #[type_const] + const Y: i32; +} + +struct Hold(T); + +trait Bound = Trait }>; + +fn main() { + let _: dyn Bound; //~ ERROR associated constant binding in trait object type mentions `Self` +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.stderr new file mode 100644 index 000000000000..109cb7602dcd --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-const-projection-behind-trait-alias-mentions-self.stderr @@ -0,0 +1,11 @@ +error: associated constant binding in trait object type mentions `Self` + --> $DIR/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs:21:12 + | +LL | trait Bound = Trait }>; + | -------------------- this binding mentions `Self` +... +LL | let _: dyn Bound; + | ^^^^^^^^^ contains a mention of `Self` + +error: aborting due to 1 previous error + diff --git a/tests/ui/dyn-compatibility/trait-alias-self-projection.rs b/tests/ui/dyn-compatibility/trait-alias-self-projection.rs deleted file mode 100644 index 0badb738809e..000000000000 --- a/tests/ui/dyn-compatibility/trait-alias-self-projection.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![feature(trait_alias)] -trait B = Fn() -> Self; -type D = &'static dyn B; -//~^ ERROR E0411 - -fn a() -> D { - unreachable!(); -} - -fn main() { - _ = a(); -} diff --git a/tests/ui/dyn-compatibility/trait-alias-self-projection.stderr b/tests/ui/dyn-compatibility/trait-alias-self-projection.stderr deleted file mode 100644 index dccee02e9cd1..000000000000 --- a/tests/ui/dyn-compatibility/trait-alias-self-projection.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0411]: `Self` is not allowed in type aliases - --> $DIR/trait-alias-self-projection.rs:3:19 - | -LL | type D = &'static dyn B; - | ^^^^^ `Self` is only available in impls, traits, and concrete type definitions - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0411`. diff --git a/tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.rs b/tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.rs new file mode 100644 index 000000000000..9122ddaaff7b --- /dev/null +++ b/tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.rs @@ -0,0 +1,18 @@ +// Check that we reject type projections behind trait aliases that mention `Self`. +// +// The author of the trait object type can't fix this unlike the supertrait bound +// equivalent where they just need to explicitly specify the assoc type. + +// issue: + +#![feature(trait_alias)] + +trait F = Fn() -> Self; + +trait G = H; +trait H { type T: ?Sized; } + +fn main() { + let _: dyn F; //~ ERROR associated type binding in trait object type mentions `Self` + let _: dyn G; //~ ERROR associated type binding in trait object type mentions `Self` +} diff --git a/tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.stderr b/tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.stderr new file mode 100644 index 000000000000..0b7fb55a908c --- /dev/null +++ b/tests/ui/dyn-compatibility/type-projection-behind-trait-alias-mentions-self.stderr @@ -0,0 +1,20 @@ +error: associated type binding in trait object type mentions `Self` + --> $DIR/type-projection-behind-trait-alias-mentions-self.rs:16:12 + | +LL | trait F = Fn() -> Self; + | ---- this binding mentions `Self` +... +LL | let _: dyn F; + | ^^^^^ contains a mention of `Self` + +error: associated type binding in trait object type mentions `Self` + --> $DIR/type-projection-behind-trait-alias-mentions-self.rs:17:12 + | +LL | trait G = H; + | -------- this binding mentions `Self` +... +LL | let _: dyn G; + | ^^^^^ contains a mention of `Self` + +error: aborting due to 2 previous errors + From 7f5819317b153c6518c84859059c8e91f9949fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 19 Jan 2026 20:13:48 +0100 Subject: [PATCH 161/273] mGCA: Render traits dyn incompatible if the ty of an assoc const refs `Self` (barring `Self` projections) --- .../src/hir_ty_lowering/dyn_trait.rs | 5 + compiler/rustc_middle/src/traits/mod.rs | 169 ++++++++---------- .../src/traits/dyn_compatibility.rs | 73 +++++--- ...dyn-compat-assoc-const-ty-mentions-self.rs | 38 ++++ ...compat-assoc-const-ty-mentions-self.stderr | 24 +++ ...und-on-assoc-const-allowed-and-enforced.rs | 24 +++ ...on-assoc-const-allowed-and-enforced.stderr | 20 +++ ...elf-const-projections-in-assoc-const-ty.rs | 30 ++++ ...const-projections-in-assoc-const-ty.stderr | 27 +++ 9 files changed, 292 insertions(+), 118 deletions(-) create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.stderr create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.stderr create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.rs create mode 100644 tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index c1cbc44953ad..29f29761b605 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -58,6 +58,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let mut user_written_bounds = Vec::new(); let mut potential_assoc_items = Vec::new(); for poly_trait_ref in hir_bounds.iter() { + // FIXME(mgca): We actually leak `trait_object_dummy_self` if the type of any assoc + // const mentions `Self` (in "Self projections" which we intentionally + // allow). That's because we query feed the instantiated type to `type_of`. + // That's really bad, dummy self should never escape lowering! It leads us + // to accept less code we'd like to support and can lead to ICEs later. let result = self.lower_poly_trait_ref( poly_trait_ref, dummy_self, diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 429db832884b..3a1682614cbf 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -761,12 +761,12 @@ pub struct ImplSourceUserDefinedData<'tcx, N> { #[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable)] pub enum DynCompatibilityViolation { - /// `Self: Sized` declared on the trait. - SizedSelf(SmallVec<[Span; 1]>), - /// Trait is marked `#[rustc_dyn_incompatible_trait]`. ExplicitlyDynIncompatible(SmallVec<[Span; 1]>), + /// `Self: Sized` declared on the trait. + SizedSelf(SmallVec<[Span; 1]>), + /// Supertrait reference references `Self` an in illegal location /// (e.g., `trait Foo : Bar`). SupertraitSelf(SmallVec<[Span; 1]>), @@ -778,29 +778,24 @@ pub enum DynCompatibilityViolation { SupertraitConst(SmallVec<[Span; 1]>), /// Method has something illegal. - Method(Symbol, MethodViolationCode, Span), + Method(Symbol, MethodViolation, Span), - /// Associated constant. - AssocConst(Symbol, Span), + /// Associated constant is faulty. + AssocConst(Symbol, AssocConstViolation, Span), - /// Generic associated constant. - GenericAssocConst(Symbol, Span), - - /// Associated constant that wasn't marked `#[type_const]`. - NonTypeAssocConst(Symbol, Span), - - /// Generic associated type. + /// Generic associated type (GAT). GenericAssocTy(Symbol, Span), } impl DynCompatibilityViolation { pub fn error_msg(&self) -> Cow<'static, str> { + // FIXME(mgca): For method violations we just say "method ..." but for assoc const ones we + // say "it contains ... associated constant ...". Make it consistent. + match self { - DynCompatibilityViolation::SizedSelf(_) => "it requires `Self: Sized`".into(), - DynCompatibilityViolation::ExplicitlyDynIncompatible(_) => { - "it opted out of dyn-compatibility".into() - } - DynCompatibilityViolation::SupertraitSelf(spans) => { + Self::ExplicitlyDynIncompatible(_) => "it opted out of dyn-compatibility".into(), + Self::SizedSelf(_) => "it requires `Self: Sized`".into(), + Self::SupertraitSelf(spans) => { if spans.iter().any(|sp| *sp != DUMMY_SP) { "it uses `Self` as a type parameter".into() } else { @@ -808,67 +803,55 @@ impl DynCompatibilityViolation { .into() } } - DynCompatibilityViolation::SupertraitNonLifetimeBinder(_) => { + Self::SupertraitNonLifetimeBinder(_) => { "where clause cannot reference non-lifetime `for<...>` variables".into() } - DynCompatibilityViolation::SupertraitConst(_) => { - "it cannot have a `const` supertrait".into() - } - DynCompatibilityViolation::Method(name, MethodViolationCode::StaticMethod(_), _) => { + Self::SupertraitConst(_) => "it cannot have a `const` supertrait".into(), + Self::Method(name, MethodViolation::StaticMethod(_), _) => { format!("associated function `{name}` has no `self` parameter").into() } - DynCompatibilityViolation::Method( - name, - MethodViolationCode::ReferencesSelfInput(_), - DUMMY_SP, - ) => format!("method `{name}` references the `Self` type in its parameters").into(), - DynCompatibilityViolation::Method( - name, - MethodViolationCode::ReferencesSelfInput(_), - _, - ) => format!("method `{name}` references the `Self` type in this parameter").into(), - DynCompatibilityViolation::Method( - name, - MethodViolationCode::ReferencesSelfOutput, - _, - ) => format!("method `{name}` references the `Self` type in its return type").into(), - DynCompatibilityViolation::Method( - name, - MethodViolationCode::ReferencesImplTraitInTrait(_), - _, - ) => { + Self::Method(name, MethodViolation::ReferencesSelfInput(_), DUMMY_SP) => { + format!("method `{name}` references the `Self` type in its parameters").into() + } + Self::Method(name, MethodViolation::ReferencesSelfInput(_), _) => { + format!("method `{name}` references the `Self` type in this parameter").into() + } + Self::Method(name, MethodViolation::ReferencesSelfOutput, _) => { + format!("method `{name}` references the `Self` type in its return type").into() + } + Self::Method(name, MethodViolation::ReferencesImplTraitInTrait(_), _) => { format!("method `{name}` references an `impl Trait` type in its return type").into() } - DynCompatibilityViolation::Method(name, MethodViolationCode::AsyncFn, _) => { + Self::Method(name, MethodViolation::AsyncFn, _) => { format!("method `{name}` is `async`").into() } - DynCompatibilityViolation::Method(name, MethodViolationCode::CVariadic, _) => { + Self::Method(name, MethodViolation::CVariadic, _) => { format!("method `{name}` is C-variadic").into() } - DynCompatibilityViolation::Method( - name, - MethodViolationCode::WhereClauseReferencesSelf, - _, - ) => format!("method `{name}` references the `Self` type in its `where` clause").into(), - DynCompatibilityViolation::Method(name, MethodViolationCode::Generic, _) => { + Self::Method(name, MethodViolation::WhereClauseReferencesSelf, _) => { + format!("method `{name}` references the `Self` type in its `where` clause").into() + } + Self::Method(name, MethodViolation::Generic, _) => { format!("method `{name}` has generic type parameters").into() } - DynCompatibilityViolation::Method( - name, - MethodViolationCode::UndispatchableReceiver(_), - _, - ) => format!("method `{name}`'s `self` parameter cannot be dispatched on").into(), - DynCompatibilityViolation::AssocConst(name, _) => { + Self::Method(name, MethodViolation::UndispatchableReceiver(_), _) => { + format!("method `{name}`'s `self` parameter cannot be dispatched on").into() + } + Self::AssocConst(name, AssocConstViolation::FeatureNotEnabled, _) => { format!("it contains associated const `{name}`").into() } - DynCompatibilityViolation::GenericAssocConst(name, _) => { + Self::AssocConst(name, AssocConstViolation::Generic, _) => { format!("it contains generic associated const `{name}`").into() } - DynCompatibilityViolation::NonTypeAssocConst(name, _) => { + Self::AssocConst(name, AssocConstViolation::NonType, _) => { format!("it contains associated const `{name}` that's not marked `#[type_const]`") .into() } - DynCompatibilityViolation::GenericAssocTy(name, _) => { + Self::AssocConst(name, AssocConstViolation::TypeReferencesSelf, _) => format!( + "it contains associated const `{name}` whose type references the `Self` type" + ) + .into(), + Self::GenericAssocTy(name, _) => { format!("it contains generic associated type `{name}`").into() } } @@ -876,32 +859,24 @@ impl DynCompatibilityViolation { pub fn solution(&self) -> DynCompatibilityViolationSolution { match self { - DynCompatibilityViolation::SizedSelf(_) - | DynCompatibilityViolation::ExplicitlyDynIncompatible(_) - | DynCompatibilityViolation::SupertraitSelf(_) - | DynCompatibilityViolation::SupertraitNonLifetimeBinder(..) - | DynCompatibilityViolation::SupertraitConst(_) => { - DynCompatibilityViolationSolution::None - } - DynCompatibilityViolation::Method( + Self::ExplicitlyDynIncompatible(_) + | Self::SizedSelf(_) + | Self::SupertraitSelf(_) + | Self::SupertraitNonLifetimeBinder(..) + | Self::SupertraitConst(_) => DynCompatibilityViolationSolution::None, + Self::Method( name, - MethodViolationCode::StaticMethod(Some((add_self_sugg, make_sized_sugg))), + MethodViolation::StaticMethod(Some((add_self_sugg, make_sized_sugg))), _, ) => DynCompatibilityViolationSolution::AddSelfOrMakeSized { name: *name, add_self_sugg: add_self_sugg.clone(), make_sized_sugg: make_sized_sugg.clone(), }, - DynCompatibilityViolation::Method( - name, - MethodViolationCode::UndispatchableReceiver(Some(span)), - _, - ) => DynCompatibilityViolationSolution::ChangeToRefSelf(*name, *span), - DynCompatibilityViolation::AssocConst(name, _) - | DynCompatibilityViolation::GenericAssocConst(name, _) - | DynCompatibilityViolation::NonTypeAssocConst(name, _) - | DynCompatibilityViolation::GenericAssocTy(name, _) - | DynCompatibilityViolation::Method(name, ..) => { + Self::Method(name, MethodViolation::UndispatchableReceiver(Some(span)), _) => { + DynCompatibilityViolationSolution::ChangeToRefSelf(*name, *span) + } + Self::Method(name, ..) | Self::AssocConst(name, ..) | Self::GenericAssocTy(name, _) => { DynCompatibilityViolationSolution::MoveToAnotherTrait(*name) } } @@ -911,16 +886,14 @@ impl DynCompatibilityViolation { // When `span` comes from a separate crate, it'll be `DUMMY_SP`. Treat it as `None` so // diagnostics use a `note` instead of a `span_label`. match self { - DynCompatibilityViolation::SupertraitSelf(spans) - | DynCompatibilityViolation::SizedSelf(spans) - | DynCompatibilityViolation::ExplicitlyDynIncompatible(spans) - | DynCompatibilityViolation::SupertraitNonLifetimeBinder(spans) - | DynCompatibilityViolation::SupertraitConst(spans) => spans.clone(), - DynCompatibilityViolation::AssocConst(_, span) - | DynCompatibilityViolation::GenericAssocConst(_, span) - | DynCompatibilityViolation::NonTypeAssocConst(_, span) - | DynCompatibilityViolation::GenericAssocTy(_, span) - | DynCompatibilityViolation::Method(_, _, span) => { + Self::ExplicitlyDynIncompatible(spans) + | Self::SizedSelf(spans) + | Self::SupertraitSelf(spans) + | Self::SupertraitNonLifetimeBinder(spans) + | Self::SupertraitConst(spans) => spans.clone(), + Self::Method(_, _, span) + | Self::AssocConst(_, _, span) + | Self::GenericAssocTy(_, span) => { if *span != DUMMY_SP { smallvec![*span] } else { @@ -987,7 +960,7 @@ impl DynCompatibilityViolationSolution { /// Reasons a method might not be dyn-compatible. #[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable)] -pub enum MethodViolationCode { +pub enum MethodViolation { /// e.g., `fn foo()` StaticMethod(Option<(/* add &self */ (String, Span), /* add Self: Sized */ (String, Span))>), @@ -1016,6 +989,22 @@ pub enum MethodViolationCode { UndispatchableReceiver(Option), } +/// Reasons an associated const might not be dyn compatible. +#[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable)] +pub enum AssocConstViolation { + /// Unstable feature `min_generic_const_args` wasn't enabled. + FeatureNotEnabled, + + /// Has own generic parameters (GAC). + Generic, + + /// Isn't marked `#[type_const]`. + NonType, + + /// Its type mentions the `Self` type parameter. + TypeReferencesSelf, +} + /// These are the error cases for `codegen_select_candidate`. #[derive(Copy, Clone, Debug, Hash, HashStable, Encodable, Decodable)] pub enum CodegenObligationError { diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index e8f0dd57353c..be70612653ce 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -25,7 +25,8 @@ use crate::infer::TyCtxtInferExt; pub use crate::traits::DynCompatibilityViolation; use crate::traits::query::evaluate_obligation::InferCtxtExt; use crate::traits::{ - MethodViolationCode, Obligation, ObligationCause, normalize_param_env_or_error, util, + AssocConstViolation, MethodViolation, Obligation, ObligationCause, + normalize_param_env_or_error, util, }; /// Returns the dyn-compatibility violations that affect HIR ty lowering. @@ -230,7 +231,7 @@ fn predicate_references_self<'tcx>( // types for trait objects. // // Note that we *do* allow projection *outputs* to contain - // `self` (i.e., `trait Foo: Bar { type Result; }`), + // `Self` (i.e., `trait Foo: Bar { type Result; }`), // we just require the user to specify *both* outputs // in the object type (i.e., `dyn Foo`). // @@ -322,20 +323,36 @@ pub fn dyn_compatibility_violations_for_assoc_item( match item.kind { ty::AssocKind::Const { name } => { + // We will permit type associated consts if they are explicitly mentioned in the + // trait object type. We can't check this here, as here we only check if it is + // guaranteed to not be possible. + + let mut errors = Vec::new(); + if tcx.features().min_generic_const_args() { if !tcx.generics_of(item.def_id).is_own_empty() { - vec![DynCompatibilityViolation::GenericAssocConst(name, span())] + errors.push(AssocConstViolation::Generic); } else if !find_attr!(tcx.get_all_attrs(item.def_id), AttributeKind::TypeConst(_)) { - vec![DynCompatibilityViolation::NonTypeAssocConst(name, span())] - } else { - // We will permit type associated consts if they are explicitly mentioned in the - // trait object type. We can't check this here, as here we only check if it is - // guaranteed to not be possible. - Vec::new() + errors.push(AssocConstViolation::NonType); + } + + let ty = ty::Binder::dummy(tcx.type_of(item.def_id).instantiate_identity()); + if contains_illegal_self_type_reference( + tcx, + trait_def_id, + ty, + AllowSelfProjections::Yes, + ) { + errors.push(AssocConstViolation::TypeReferencesSelf); } } else { - vec![DynCompatibilityViolation::AssocConst(name, span())] + errors.push(AssocConstViolation::FeatureNotEnabled); } + + errors + .into_iter() + .map(|error| DynCompatibilityViolation::AssocConst(name, error, span())) + .collect() } ty::AssocKind::Fn { name, .. } => { virtual_call_violations_for_method(tcx, trait_def_id, item) @@ -344,10 +361,10 @@ pub fn dyn_compatibility_violations_for_assoc_item( let node = tcx.hir_get_if_local(item.def_id); // Get an accurate span depending on the violation. let span = match (&v, node) { - (MethodViolationCode::ReferencesSelfInput(Some(span)), _) => *span, - (MethodViolationCode::UndispatchableReceiver(Some(span)), _) => *span, - (MethodViolationCode::ReferencesImplTraitInTrait(span), _) => *span, - (MethodViolationCode::ReferencesSelfOutput, Some(node)) => { + (MethodViolation::ReferencesSelfInput(Some(span)), _) => *span, + (MethodViolation::UndispatchableReceiver(Some(span)), _) => *span, + (MethodViolation::ReferencesImplTraitInTrait(span), _) => *span, + (MethodViolation::ReferencesSelfOutput, Some(node)) => { node.fn_decl().map_or(item.ident(tcx).span, |decl| decl.output.span()) } _ => span(), @@ -380,7 +397,7 @@ fn virtual_call_violations_for_method<'tcx>( tcx: TyCtxt<'tcx>, trait_def_id: DefId, method: ty::AssocItem, -) -> Vec { +) -> Vec { let sig = tcx.fn_sig(method.def_id).instantiate_identity(); // The method's first parameter must be named `self` @@ -408,7 +425,7 @@ fn virtual_call_violations_for_method<'tcx>( // Not having `self` parameter messes up the later checks, // so we need to return instead of pushing - return vec![MethodViolationCode::StaticMethod(sugg)]; + return vec![MethodViolation::StaticMethod(sugg)]; } let mut errors = Vec::new(); @@ -429,7 +446,7 @@ fn virtual_call_violations_for_method<'tcx>( } else { None }; - errors.push(MethodViolationCode::ReferencesSelfInput(span)); + errors.push(MethodViolation::ReferencesSelfInput(span)); } } if contains_illegal_self_type_reference( @@ -438,19 +455,19 @@ fn virtual_call_violations_for_method<'tcx>( sig.output(), AllowSelfProjections::Yes, ) { - errors.push(MethodViolationCode::ReferencesSelfOutput); + errors.push(MethodViolation::ReferencesSelfOutput); } - if let Some(code) = contains_illegal_impl_trait_in_trait(tcx, method.def_id, sig.output()) { - errors.push(code); + if let Some(error) = contains_illegal_impl_trait_in_trait(tcx, method.def_id, sig.output()) { + errors.push(error); } if sig.skip_binder().c_variadic { - errors.push(MethodViolationCode::CVariadic); + errors.push(MethodViolation::CVariadic); } // We can't monomorphize things like `fn foo(...)`. let own_counts = tcx.generics_of(method.def_id).own_counts(); if own_counts.types > 0 || own_counts.consts > 0 { - errors.push(MethodViolationCode::Generic); + errors.push(MethodViolation::Generic); } let receiver_ty = tcx.liberate_late_bound_regions(method.def_id, sig.input(0)); @@ -470,7 +487,7 @@ fn virtual_call_violations_for_method<'tcx>( } else { None }; - errors.push(MethodViolationCode::UndispatchableReceiver(span)); + errors.push(MethodViolation::UndispatchableReceiver(span)); } else { // We confirm that the `receiver_is_dispatchable` is accurate later, // see `check_receiver_correct`. It should be kept in sync with this code. @@ -528,7 +545,7 @@ fn virtual_call_violations_for_method<'tcx>( contains_illegal_self_type_reference(tcx, trait_def_id, pred, AllowSelfProjections::Yes) }) { - errors.push(MethodViolationCode::WhereClauseReferencesSelf); + errors.push(MethodViolation::WhereClauseReferencesSelf); } errors @@ -870,12 +887,12 @@ fn contains_illegal_impl_trait_in_trait<'tcx>( tcx: TyCtxt<'tcx>, fn_def_id: DefId, ty: ty::Binder<'tcx, Ty<'tcx>>, -) -> Option { +) -> Option { let ty = tcx.liberate_late_bound_regions(fn_def_id, ty); if tcx.asyncness(fn_def_id).is_async() { // Rendering the error as a separate `async-specific` message is better. - Some(MethodViolationCode::AsyncFn) + Some(MethodViolation::AsyncFn) } else { ty.visit_with(&mut IllegalRpititVisitor { tcx, allowed: None }).break_value() } @@ -887,14 +904,14 @@ struct IllegalRpititVisitor<'tcx> { } impl<'tcx> TypeVisitor> for IllegalRpititVisitor<'tcx> { - type Result = ControlFlow; + type Result = ControlFlow; fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { if let ty::Alias(ty::Projection, proj) = *ty.kind() && Some(proj) != self.allowed && self.tcx.is_impl_trait_in_trait(proj.def_id) { - ControlFlow::Break(MethodViolationCode::ReferencesImplTraitInTrait( + ControlFlow::Break(MethodViolation::ReferencesImplTraitInTrait( self.tcx.def_span(proj.def_id), )) } else { diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.rs new file mode 100644 index 000000000000..d19e7acbaff0 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.rs @@ -0,0 +1,38 @@ +// Ensure that we consider traits dyn *in*compatible if the type of any (type) assoc const +// mentions `Self` (barring "`Self` projections") + +//@ dont-require-annotations: NOTE + +#![feature(generic_const_items)] +#![feature(generic_const_parameter_types)] +#![feature(min_generic_const_args)] +#![feature(unsized_const_params)] +#![expect(incomplete_features)] + +trait Trait { + // NOTE: The `ConstParamTy_` bound is intentionally on the assoc const and not on the trait as + // doing the latter would already render the trait dyn incompatible due to it being + // bounded by `PartialEq` and supertrait bounds cannot mention `Self` like this. + #[type_const] + const K: Self where Self: std::marker::ConstParamTy_; + //~^ NOTE it contains associated const `K` whose type references the `Self` type + + // This is not a "`Self` projection" in our sense (which would be allowed) + // since the trait is not the principal trait or a supertrait thereof. + #[type_const] + const Q: ::Output; + //~^ NOTE it contains associated const `Q` whose type references the `Self` type +} + +trait SomeOtherTrait { + type Output: std::marker::ConstParamTy_; +} + +// You could imagine this impl being more interesting and mention `T` somewhere in `Output`... +impl SomeOtherTrait for T { + type Output = (); +} + +fn main() { + let _: dyn Trait; //~ ERROR the trait `Trait` is not dyn compatible +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.stderr new file mode 100644 index 000000000000..dba1643d2c5c --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-assoc-const-ty-mentions-self.stderr @@ -0,0 +1,24 @@ +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-assoc-const-ty-mentions-self.rs:37:16 + | +LL | let _: dyn Trait; + | ^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-assoc-const-ty-mentions-self.rs:17:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +... +LL | const K: Self where Self: std::marker::ConstParamTy_; + | ^ ...because it contains associated const `K` whose type references the `Self` type +... +LL | const Q: ::Output; + | ^ ...because it contains associated const `Q` whose type references the `Self` type + = help: consider moving `K` to another trait + = help: consider moving `Q` to another trait + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs new file mode 100644 index 000000000000..07ce629ab7be --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs @@ -0,0 +1,24 @@ +// Ensure that the where-clause of assoc consts in dyn-compatible traits are allowed to freely +// reference the `Self` type parameter (contrary to methods) and that such where clauses are +// actually enforced. + +#![feature(min_generic_const_args, generic_const_items)] +#![expect(incomplete_features)] + +trait Trait { + #[type_const] + const N: i32 where Self: Bound; +} + +impl Trait for () { + #[type_const] + const N: i32 = 0; +} + +trait Bound {} + +fn main() { + let _: dyn Trait; // OK + + let _: &dyn Trait = &(); //~ ERROR the trait bound `(): Bound` is not satisfied +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.stderr new file mode 100644 index 000000000000..8eeb60d55c38 --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.stderr @@ -0,0 +1,20 @@ +error[E0277]: the trait bound `(): Bound` is not satisfied + --> $DIR/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs:23:32 + | +LL | let _: &dyn Trait = &(); + | ^^^ the trait `Bound` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs:18:1 + | +LL | trait Bound {} + | ^^^^^^^^^^^ +note: required by a bound in `Trait::N` + --> $DIR/dyn-compat-self-bound-on-assoc-const-allowed-and-enforced.rs:10:30 + | +LL | const N: i32 where Self: Bound; + | ^^^^^ required by this bound in `Trait::N` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.rs new file mode 100644 index 000000000000..936556e957ca --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.rs @@ -0,0 +1,30 @@ +// FIXME(mgca): Ideally this would compile -- at least if the user annotated the instantiated type +// of the assoc const (but we don't have the syntax for this (yet)). In any case, we +// should not leak `trait_object_dummy_self` (defined as `FreshTy(0)` under the hood) +// to the rest of the compiler and by extension the user via diagnostics. +//@ known-bug: unknown + +#![feature(min_generic_const_args, unsized_const_params, generic_const_parameter_types)] +#![expect(incomplete_features)] + +trait A { + type Ty: std::marker::ConstParamTy_; + #[type_const] const CT: Self::Ty; +} + +impl A for () { + type Ty = i32; + #[type_const] const CT: i32 = 0; +} + +fn main() { + // NOTE: As alluded to above, if we can't get the examples below to compile as written, + // we might want to allow the user to manually specify the instantiated type somehow. + // The hypothetical syntax for that *might* look sth. like + // * `dyn A i32 { 0 }>` + // * `dyn A` + + let _: dyn A; + + let _: &dyn A = &(); +} diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.stderr new file mode 100644 index 000000000000..8ee231ec070f --- /dev/null +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-self-const-projections-in-assoc-const-ty.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `FreshTy(0): A` is not satisfied + --> $DIR/dyn-compat-self-const-projections-in-assoc-const-ty.rs:27:33 + | +LL | let _: dyn A; + | ^ the trait `A` is not implemented for `FreshTy(0)` + | +help: the trait `A` is implemented for `()` + --> $DIR/dyn-compat-self-const-projections-in-assoc-const-ty.rs:15:1 + | +LL | impl A for () { + | ^^^^^^^^^^^^^ + +error[E0277]: the trait bound `FreshTy(0): A` is not satisfied + --> $DIR/dyn-compat-self-const-projections-in-assoc-const-ty.rs:29:34 + | +LL | let _: &dyn A = &(); + | ^ the trait `A` is not implemented for `FreshTy(0)` + | +help: the trait `A` is implemented for `()` + --> $DIR/dyn-compat-self-const-projections-in-assoc-const-ty.rs:15:1 + | +LL | impl A for () { + | ^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. From 3ccabc6a8dc5c65a0e8cb4fbebd300d5dcf4fed3 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:27:49 +0000 Subject: [PATCH 162/273] Remove old error emitter This completes the transition to annotate-snippets --- Cargo.lock | 1 - compiler/rustc_driver_impl/src/lib.rs | 9 +- compiler/rustc_errors/Cargo.toml | 1 - compiler/rustc_errors/src/diagnostic.rs | 3 +- compiler/rustc_errors/src/emitter.rs | 2929 +------------------- compiler/rustc_errors/src/lib.rs | 294 +- compiler/rustc_errors/src/snippet.rs | 214 -- compiler/rustc_errors/src/styled_buffer.rs | 163 -- compiler/rustc_errors/src/translation.rs | 3 +- compiler/rustc_parse/src/parser/tests.rs | 22 +- compiler/rustc_session/src/parse.rs | 11 +- src/librustdoc/doctest/make.rs | 4 +- src/tools/rustfmt/src/parse/session.rs | 5 +- 13 files changed, 53 insertions(+), 3606 deletions(-) delete mode 100644 compiler/rustc_errors/src/snippet.rs delete mode 100644 compiler/rustc_errors/src/styled_buffer.rs diff --git a/Cargo.lock b/Cargo.lock index 833356011964..9dc507f15662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3865,7 +3865,6 @@ dependencies = [ "rustc_fluent_macro", "rustc_hashes", "rustc_index", - "rustc_lexer", "rustc_lint_defs", "rustc_macros", "rustc_serialize", diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 5dee64b42f73..43b2cd682910 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -1534,10 +1534,11 @@ fn report_ice( using_internal_features: &AtomicBool, ) { let translator = default_translator(); - let emitter = Box::new(rustc_errors::emitter::HumanEmitter::new( - stderr_destination(rustc_errors::ColorConfig::Auto), - translator, - )); + let emitter = + Box::new(rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter::new( + stderr_destination(rustc_errors::ColorConfig::Auto), + translator, + )); let dcx = rustc_errors::DiagCtxt::new(emitter); let dcx = dcx.handle(); diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index 6c5a1740a9a6..81eecca3199f 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -17,7 +17,6 @@ rustc_error_messages = { path = "../rustc_error_messages" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hashes = { path = "../rustc_hashes" } rustc_index = { path = "../rustc_index" } -rustc_lexer = { path = "../rustc_lexer" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 4365ceaff22d..a9e81354fc6a 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -15,10 +15,9 @@ use rustc_span::source_map::Spanned; use rustc_span::{DUMMY_SP, Span, Symbol}; use tracing::debug; -use crate::snippet::Style; use crate::{ CodeSuggestion, DiagCtxtHandle, DiagMessage, ErrCode, ErrorGuaranteed, ExplicitBug, Level, - MultiSpan, StashKey, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle, + MultiSpan, StashKey, Style, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle, Suggestions, }; diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 2e41f74ee25d..3433db1e0704 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -8,42 +8,29 @@ //! The output types are defined in `rustc_session::config::ErrorOutputType`. use std::borrow::Cow; -use std::cmp::{Reverse, max, min}; use std::error::Report; use std::io::prelude::*; use std::io::{self, IsTerminal}; use std::iter; use std::path::Path; -use std::sync::Arc; use anstream::{AutoStream, ColorChoice}; use anstyle::{AnsiColor, Effects}; -use derive_setters::Setters; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; -use rustc_data_structures::sync::{DynSend, IntoDynSyncSend}; -use rustc_error_messages::{FluentArgs, SpanLabel}; -use rustc_lexer; -use rustc_lint_defs::pluralize; +use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::sync::DynSend; +use rustc_error_messages::FluentArgs; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; -use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width}; -use tracing::{debug, instrument, trace, warn}; +use rustc_span::{FileName, SourceFile, Span}; +use tracing::{debug, warn}; use crate::registry::Registry; -use crate::snippet::{ - Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString, -}; -use crate::styled_buffer::StyledBuffer; use crate::timings::TimingRecord; -use crate::translation::{Translator, to_fluent_args}; +use crate::translation::Translator; use crate::{ - CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag, - SubstitutionHighlight, SuggestionStyle, TerminalUrl, + CodeSuggestion, DiagInner, DiagMessage, Level, MultiSpan, Style, Subdiag, SuggestionStyle, }; -/// Default column width, used in tests and when terminal dimensions cannot be determined. -const DEFAULT_COLUMN_WIDTH: usize = 140; - /// Describes the way the content of the `rendered` field of the json output is generated #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HumanReadableErrorType { @@ -57,119 +44,11 @@ impl HumanReadableErrorType { } } -#[derive(Clone, Copy, Debug)] -struct Margin { - /// The available whitespace in the left that can be consumed when centering. - pub whitespace_left: usize, - /// The column of the beginning of leftmost span. - pub span_left: usize, - /// The column of the end of rightmost span. - pub span_right: usize, - /// The beginning of the line to be displayed. - pub computed_left: usize, - /// The end of the line to be displayed. - pub computed_right: usize, - /// The current width of the terminal. Uses value of `DEFAULT_COLUMN_WIDTH` constant by default - /// and in tests. - pub column_width: usize, - /// The end column of a span label, including the span. Doesn't account for labels not in the - /// same line as the span. - pub label_right: usize, -} - -impl Margin { - fn new( - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - ) -> Self { - // The 6 is padding to give a bit of room for `...` when displaying: - // ``` - // error: message - // --> file.rs:16:58 - // | - // 16 | ... fn foo(self) -> Self::Bar { - // | ^^^^^^^^^ - // ``` - - let mut m = Margin { - whitespace_left: whitespace_left.saturating_sub(6), - span_left: span_left.saturating_sub(6), - span_right: span_right + 6, - computed_left: 0, - computed_right: 0, - column_width, - label_right: label_right + 6, - }; - m.compute(max_line_len); - m - } - - fn was_cut_left(&self) -> bool { - self.computed_left > 0 - } - - fn compute(&mut self, max_line_len: usize) { - // When there's a lot of whitespace (>20), we want to trim it as it is useless. - // FIXME: this doesn't account for '\t', but to do so correctly we need to perform that - // calculation later, right before printing in order to be accurate with both unicode - // handling and trimming of long lines. - self.computed_left = if self.whitespace_left > 20 { - self.whitespace_left - 16 // We want some padding. - } else { - 0 - }; - // We want to show as much as possible, max_line_len is the rightmost boundary for the - // relevant code. - self.computed_right = max(max_line_len, self.computed_left); - - if self.computed_right - self.computed_left > self.column_width { - // Trimming only whitespace isn't enough, let's get craftier. - if self.label_right - self.whitespace_left <= self.column_width { - // Attempt to fit the code window only trimming whitespace. - self.computed_left = self.whitespace_left; - self.computed_right = self.computed_left + self.column_width; - } else if self.label_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering only the spans and labels. - let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else if self.span_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering the spans and labels plus padding. - let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else { - // Mostly give up but still don't show the full line. - self.computed_left = self.span_left; - self.computed_right = self.span_right; - } - } - } - - fn left(&self, line_len: usize) -> usize { - min(self.computed_left, line_len) - } - - fn right(&self, line_len: usize) -> usize { - if line_len.saturating_sub(self.computed_left) <= self.column_width { - line_len - } else { - min(line_len, self.computed_right) - } - } -} - pub enum TimingEvent { Start, End, } -const ANONYMIZED_LINE_NUM: &str = "LL"; - pub type DynEmitter = dyn Emitter + DynSend; /// Emitter trait for emitting errors and other structured information. @@ -490,48 +369,6 @@ pub trait Emitter { } } -impl Emitter for HumanEmitter { - fn source_map(&self) -> Option<&SourceMap> { - self.sm.as_deref() - } - - fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) { - let fluent_args = to_fluent_args(diag.args.iter()); - - if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() { - diag.children.insert(0, diag.emitted_at_sub_diag()); - } - - let mut suggestions = diag.suggestions.unwrap_tag(); - self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args); - - self.fix_multispans_in_extern_macros_and_render_macro_backtrace( - &mut diag.span, - &mut diag.children, - &diag.level, - self.macro_backtrace, - ); - - self.emit_messages_default( - &diag.level, - &diag.messages, - &fluent_args, - &diag.code, - &diag.span, - &diag.children, - &suggestions, - ); - } - - fn should_show_explain(&self) -> bool { - !self.short_message - } - - fn translator(&self) -> &Translator { - &self.translator - } -} - /// An emitter that adds a note to each diagnostic. pub struct EmitterWithNote { pub emitter: Box, @@ -604,2703 +441,6 @@ pub enum OutputTheme { Unicode, } -/// Handles the writing of `HumanReadableErrorType` -#[derive(Setters)] -pub struct HumanEmitter { - #[setters(skip)] - dst: IntoDynSyncSend, - sm: Option>, - #[setters(skip)] - translator: Translator, - short_message: bool, - ui_testing: bool, - ignored_directories_in_source_blocks: Vec, - diagnostic_width: Option, - - macro_backtrace: bool, - track_diagnostics: bool, - terminal_url: TerminalUrl, - theme: OutputTheme, -} - -#[derive(Debug)] -pub(crate) struct FileWithAnnotatedLines { - pub(crate) file: Arc, - pub(crate) lines: Vec, - multiline_depth: usize, -} - -impl HumanEmitter { - pub fn new(dst: Destination, translator: Translator) -> HumanEmitter { - HumanEmitter { - dst: IntoDynSyncSend(dst), - sm: None, - translator, - short_message: false, - ui_testing: false, - ignored_directories_in_source_blocks: Vec::new(), - diagnostic_width: None, - macro_backtrace: false, - track_diagnostics: false, - terminal_url: TerminalUrl::No, - theme: OutputTheme::Ascii, - } - } - - fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> { - if self.ui_testing { - Cow::Borrowed(ANONYMIZED_LINE_NUM) - } else { - Cow::Owned(line_num.to_string()) - } - } - - fn draw_line( - &self, - buffer: &mut StyledBuffer, - source_string: &str, - line_index: usize, - line_offset: usize, - width_offset: usize, - code_offset: usize, - margin: Margin, - ) -> usize { - let line_len = source_string.len(); - // Create the source line we will highlight. - let left = margin.left(line_len); - let right = margin.right(line_len); - // FIXME: The following code looks fishy. See #132860. - // On long lines, we strip the source line, accounting for unicode. - let code: String = source_string - .chars() - .enumerate() - .skip_while(|(i, _)| *i < left) - .take_while(|(i, _)| *i < right) - .map(|(_, c)| c) - .collect(); - let code = normalize_whitespace(&code); - let was_cut_right = - source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some(); - buffer.puts(line_offset, code_offset, &code, Style::Quotation); - let placeholder = self.margin(); - if margin.was_cut_left() { - // We have stripped some code/whitespace from the beginning, make it clear. - buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber); - } - if was_cut_right { - let padding = str_width(placeholder); - // We have stripped some code after the rightmost span end, make it clear we did so. - buffer.puts( - line_offset, - code_offset + str_width(&code) - padding, - placeholder, - Style::LineNumber, - ); - } - self.draw_line_num(buffer, line_index, line_offset, width_offset - 3); - self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2); - left - } - - #[instrument(level = "trace", skip(self), ret)] - fn render_source_line( - &self, - buffer: &mut StyledBuffer, - file: Arc, - line: &Line, - width_offset: usize, - code_offset: usize, - margin: Margin, - close_window: bool, - ) -> Vec<(usize, Style)> { - // Draw: - // - // LL | ... code ... - // | ^^-^ span label - // | | - // | secondary span label - // - // ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it - // | | | | - // | | | actual code found in your source code and the spans we use to mark it - // | | when there's too much wasted space to the left, trim it - // | vertical divider between the column number and the code - // column number - - if line.line_index == 0 { - return Vec::new(); - } - - let Some(source_string) = file.get_line(line.line_index - 1) else { - return Vec::new(); - }; - trace!(?source_string); - - let line_offset = buffer.num_lines(); - - // Left trim. - // FIXME: This looks fishy. See #132860. - let left = self.draw_line( - buffer, - &source_string, - line.line_index, - line_offset, - width_offset, - code_offset, - margin, - ); - - // Special case when there's only one annotation involved, it is the start of a multiline - // span and there's no text at the beginning of the code line. Instead of doing the whole - // graph: - // - // 2 | fn foo() { - // | _^ - // 3 | | - // 4 | | } - // | |_^ test - // - // we simplify the output to: - // - // 2 | / fn foo() { - // 3 | | - // 4 | | } - // | |_^ test - let mut buffer_ops = vec![]; - let mut annotations = vec![]; - let mut short_start = true; - for ann in &line.annotations { - if let AnnotationType::MultilineStart(depth) = ann.annotation_type { - if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) { - let uline = self.underline(ann.is_primary); - let chr = uline.multiline_whole_line; - annotations.push((depth, uline.style)); - buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style)); - } else { - short_start = false; - break; - } - } else if let AnnotationType::MultilineLine(_) = ann.annotation_type { - } else { - short_start = false; - break; - } - } - if short_start { - for (y, x, c, s) in buffer_ops { - buffer.putc(y, x, c, s); - } - return annotations; - } - - // We want to display like this: - // - // vec.push(vec.pop().unwrap()); - // --- ^^^ - previous borrow ends here - // | | - // | error occurs here - // previous borrow of `vec` occurs here - // - // But there are some weird edge cases to be aware of: - // - // vec.push(vec.pop().unwrap()); - // -------- - previous borrow ends here - // || - // |this makes no sense - // previous borrow of `vec` occurs here - // - // For this reason, we group the lines into "highlight lines" - // and "annotations lines", where the highlight lines have the `^`. - - // Sort the annotations by (start, end col) - // The labels are reversed, sort and then reversed again. - // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where - // the letter signifies the span. Here we are only sorting by the - // span and hence, the order of the elements with the same span will - // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get - // (C1, C2, B1, B2, A1, A2). All the elements with the same span are - // still ordered first to last, but all the elements with different - // spans are ordered by their spans in last to first order. Last to - // first order is important, because the jiggly lines and | are on - // the left, so the rightmost span needs to be rendered first, - // otherwise the lines would end up needing to go over a message. - - let mut annotations = line.annotations.clone(); - annotations.sort_by_key(|a| Reverse(a.start_col)); - - // First, figure out where each label will be positioned. - // - // In the case where you have the following annotations: - // - // vec.push(vec.pop().unwrap()); - // -------- - previous borrow ends here [C] - // || - // |this makes no sense [B] - // previous borrow of `vec` occurs here [A] - // - // `annotations_position` will hold [(2, A), (1, B), (0, C)]. - // - // We try, when possible, to stick the rightmost annotation at the end - // of the highlight line: - // - // vec.push(vec.pop().unwrap()); - // --- --- - previous borrow ends here - // - // But sometimes that's not possible because one of the other - // annotations overlaps it. For example, from the test - // `span_overlap_label`, we have the following annotations - // (written on distinct lines for clarity): - // - // fn foo(x: u32) { - // -------------- - // - - // - // In this case, we can't stick the rightmost-most label on - // the highlight line, or we would get: - // - // fn foo(x: u32) { - // -------- x_span - // | - // fn_span - // - // which is totally weird. Instead we want: - // - // fn foo(x: u32) { - // -------------- - // | | - // | x_span - // fn_span - // - // which is...less weird, at least. In fact, in general, if - // the rightmost span overlaps with any other span, we should - // use the "hang below" version, so we can at least make it - // clear where the span *starts*. There's an exception for this - // logic, when the labels do not have a message: - // - // fn foo(x: u32) { - // -------------- - // | - // x_span - // - // instead of: - // - // fn foo(x: u32) { - // -------------- - // | | - // | x_span - // - // - let mut overlap = vec![false; annotations.len()]; - let mut annotations_position = vec![]; - let mut line_len: usize = 0; - let mut p = 0; - for (i, annotation) in annotations.iter().enumerate() { - for (j, next) in annotations.iter().enumerate() { - if overlaps(next, annotation, 0) && j > i { - overlap[i] = true; - overlap[j] = true; - } - if overlaps(next, annotation, 0) // This label overlaps with another one and both - && annotation.has_label() // take space (they have text and are not - && j > i // multiline lines). - && p == 0 - // We're currently on the first line, move the label one line down - { - // If we're overlapping with an un-labelled annotation with the same span - // we can just merge them in the output - if next.start_col == annotation.start_col - && next.end_col == annotation.end_col - && !next.has_label() - { - continue; - } - - // This annotation needs a new line in the output. - p += 1; - break; - } - } - annotations_position.push((p, annotation)); - for (j, next) in annotations.iter().enumerate() { - if j > i { - let l = next.label.as_ref().map_or(0, |label| label.len() + 2); - if (overlaps(next, annotation, l) // Do not allow two labels to be in the same - // line if they overlap including padding, to - // avoid situations like: - // - // fn foo(x: u32) { - // -------^------ - // | | - // fn_spanx_span - // - && annotation.has_label() // Both labels must have some text, otherwise - && next.has_label()) // they are not overlapping. - // Do not add a new line if this annotation - // or the next are vertical line placeholders. - || (annotation.takes_space() // If either this or the next annotation is - && next.has_label()) // multiline start/end, move it to a new line - || (annotation.has_label() // so as not to overlap the horizontal lines. - && next.takes_space()) - || (annotation.takes_space() && next.takes_space()) - || (overlaps(next, annotation, l) - && next.end_col <= annotation.end_col - && next.has_label() - && p == 0) - // Avoid #42595. - { - // This annotation needs a new line in the output. - p += 1; - break; - } - } - } - line_len = max(line_len, p); - } - - if line_len != 0 { - line_len += 1; - } - - // If there are no annotations or the only annotations on this line are - // MultilineLine, then there's only code being shown, stop processing. - if line.annotations.iter().all(|a| a.is_line()) { - return vec![]; - } - - if annotations_position - .iter() - .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_))) - && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max() - { - // Special case the following, so that we minimize overlapping multiline spans. - // - // 3 │ X0 Y0 Z0 - // │ ┏━━━━━┛ │ │ < We are writing these lines - // │ ┃┌───────┘ │ < by reverting the "depth" of - // │ ┃│┌─────────┘ < their multiline spans. - // 4 │ ┃││ X1 Y1 Z1 - // 5 │ ┃││ X2 Y2 Z2 - // │ ┃│└────╿──│──┘ `Z` label - // │ ┃└─────│──┤ - // │ ┗━━━━━━┥ `Y` is a good letter too - // ╰╴ `X` is a good letter - for (pos, _) in &mut annotations_position { - *pos = max_pos - *pos; - } - // We know then that we don't need an additional line for the span label, saving us - // one line of vertical space. - line_len = line_len.saturating_sub(1); - } - - // Write the column separator. - // - // After this we will have: - // - // 2 | fn foo() { - // | - // | - // | - // 3 | - // 4 | } - // | - for pos in 0..=line_len { - self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2); - } - if close_window { - self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2); - } - - // Write the horizontal lines for multiline annotations - // (only the first and last lines need this). - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | - // | - // 3 | - // 4 | } - // | _ - for &(pos, annotation) in &annotations_position { - let underline = self.underline(annotation.is_primary); - let pos = pos + 1; - match annotation.annotation_type { - AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => { - let pre: usize = source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum(); - self.draw_range( - buffer, - underline.multiline_horizontal, - line_offset + pos, - width_offset + depth, - code_offset + pre, - underline.style, - ); - } - _ => {} - } - } - - // Write the vertical lines for labels that are on a different line as the underline. - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | | | - // | | - // 3 | | - // 4 | | } - // | |_ - for &(pos, annotation) in &annotations_position { - let underline = self.underline(annotation.is_primary); - let pos = pos + 1; - - let code_offset = code_offset - + source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum::(); - if pos > 1 && (annotation.has_label() || annotation.takes_space()) { - for p in line_offset + 1..=line_offset + pos { - buffer.putc( - p, - code_offset, - match annotation.annotation_type { - AnnotationType::MultilineLine(_) => underline.multiline_vertical, - _ => underline.vertical_text_line, - }, - underline.style, - ); - } - if let AnnotationType::MultilineStart(_) = annotation.annotation_type { - buffer.putc( - line_offset + pos, - code_offset, - underline.bottom_right, - underline.style, - ); - } - if let AnnotationType::MultilineEnd(_) = annotation.annotation_type - && annotation.has_label() - { - buffer.putc( - line_offset + pos, - code_offset, - underline.multiline_bottom_right_with_text, - underline.style, - ); - } - } - match annotation.annotation_type { - AnnotationType::MultilineStart(depth) => { - buffer.putc( - line_offset + pos, - width_offset + depth - 1, - underline.top_left, - underline.style, - ); - for p in line_offset + pos + 1..line_offset + line_len + 2 { - buffer.putc( - p, - width_offset + depth - 1, - underline.multiline_vertical, - underline.style, - ); - } - } - AnnotationType::MultilineEnd(depth) => { - for p in line_offset..line_offset + pos { - buffer.putc( - p, - width_offset + depth - 1, - underline.multiline_vertical, - underline.style, - ); - } - buffer.putc( - line_offset + pos, - width_offset + depth - 1, - underline.bottom_left, - underline.style, - ); - } - _ => (), - } - } - - // Write the labels on the annotations that actually have a label. - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | | - // | something about `foo` - // 3 | - // 4 | } - // | _ test - for &(pos, annotation) in &annotations_position { - let style = - if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary }; - let (pos, col) = if pos == 0 { - let pre: usize = source_string - .chars() - .take(annotation.end_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum(); - if annotation.end_col.file == 0 { - (pos + 1, (pre + 2)) - } else { - let pad = if annotation.end_col.file - annotation.start_col.file == 0 { - 2 - } else { - 1 - }; - (pos + 1, (pre + pad)) - } - } else { - let pre: usize = source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum(); - (pos + 2, pre) - }; - if let Some(ref label) = annotation.label { - buffer.puts(line_offset + pos, code_offset + col, label, style); - } - } - - // Sort from biggest span to smallest span so that smaller spans are - // represented in the output: - // - // x | fn foo() - // | ^^^---^^ - // | | | - // | | something about `foo` - // | something about `fn foo()` - annotations_position.sort_by_key(|(_, ann)| { - // Decreasing order. When annotations share the same length, prefer `Primary`. - (Reverse(ann.len()), ann.is_primary) - }); - - // Write the underlines. - // - // After this we will have: - // - // 2 | fn foo() { - // | ____-_____^ - // | | - // | something about `foo` - // 3 | - // 4 | } - // | _^ test - for &(pos, annotation) in &annotations_position { - let uline = self.underline(annotation.is_primary); - let width = annotation.end_col.file - annotation.start_col.file; - let previous: String = - source_string.chars().take(annotation.start_col.file).skip(left).collect(); - let underlined: String = - source_string.chars().skip(annotation.start_col.file).take(width).collect(); - debug!(?previous, ?underlined); - let code_offset = code_offset - + source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum::(); - let ann_width: usize = source_string - .chars() - .skip(annotation.start_col.file) - .take(width) - .map(|c| char_width(c)) - .sum(); - let ann_width = if ann_width == 0 - && matches!(annotation.annotation_type, AnnotationType::Singleline) - { - 1 - } else { - ann_width - }; - for p in 0..ann_width { - // The default span label underline. - buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style); - } - - if pos == 0 - && matches!( - annotation.annotation_type, - AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) - ) - { - // The beginning of a multiline span with its leftward moving line on the same line. - buffer.putc( - line_offset + 1, - code_offset, - match annotation.annotation_type { - AnnotationType::MultilineStart(_) => uline.top_right_flat, - AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line, - _ => panic!("unexpected annotation type: {annotation:?}"), - }, - uline.style, - ); - } else if pos != 0 - && matches!( - annotation.annotation_type, - AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) - ) - { - // The beginning of a multiline span with its leftward moving line on another line, - // so we start going down first. - buffer.putc( - line_offset + 1, - code_offset, - match annotation.annotation_type { - AnnotationType::MultilineStart(_) => uline.multiline_start_down, - AnnotationType::MultilineEnd(_) => uline.multiline_end_up, - _ => panic!("unexpected annotation type: {annotation:?}"), - }, - uline.style, - ); - } else if pos != 0 && annotation.has_label() { - // The beginning of a span label with an actual label, we'll point down. - buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style); - } - } - - // We look for individual *long* spans, and we trim the *middle*, so that we render - // LL | ...= [0, 0, 0, ..., 0, 0]; - // | ^^^^^^^^^^...^^^^^^^ expected `&[u8]`, found `[{integer}; 1680]` - for (i, (_pos, annotation)) in annotations_position.iter().enumerate() { - // Skip cases where multiple spans overlap each other. - if overlap[i] { - continue; - }; - let AnnotationType::Singleline = annotation.annotation_type else { continue }; - let width = annotation.end_col.display - annotation.start_col.display; - if width > margin.column_width * 2 && width > 10 { - // If the terminal is *too* small, we keep at least a tiny bit of the span for - // display. - let pad = max(margin.column_width / 3, 5); - // Code line - buffer.replace( - line_offset, - annotation.start_col.file + pad, - annotation.end_col.file - pad, - self.margin(), - ); - // Underline line - buffer.replace( - line_offset + 1, - annotation.start_col.file + pad, - annotation.end_col.file - pad, - self.margin(), - ); - } - } - annotations_position - .iter() - .filter_map(|&(_, annotation)| match annotation.annotation_type { - AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => { - let style = if annotation.is_primary { - Style::LabelPrimary - } else { - Style::LabelSecondary - }; - Some((p, style)) - } - _ => None, - }) - .collect::>() - } - - fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize { - let Some(ref sm) = self.sm else { - return 0; - }; - - let will_be_emitted = |span: Span| { - !span.is_dummy() && { - let file = sm.lookup_source_file(span.hi()); - should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file) - } - }; - - let mut max = 0; - for primary_span in msp.primary_spans() { - if will_be_emitted(*primary_span) { - let hi = sm.lookup_char_pos(primary_span.hi()); - max = (hi.line).max(max); - } - } - if !self.short_message { - for span_label in msp.span_labels() { - if will_be_emitted(span_label.span) { - let hi = sm.lookup_char_pos(span_label.span.hi()); - max = (hi.line).max(max); - } - } - } - - max - } - - fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize { - let primary = self.get_multispan_max_line_num(span); - children - .iter() - .map(|sub| self.get_multispan_max_line_num(&sub.span)) - .max() - .unwrap_or(0) - .max(primary) - } - - /// Adds a left margin to every line but the first, given a padding length and the label being - /// displayed, keeping the provided highlighting. - fn msgs_to_buffer( - &self, - buffer: &mut StyledBuffer, - msgs: &[(DiagMessage, Style)], - args: &FluentArgs<'_>, - padding: usize, - label: &str, - override_style: Option