From 0defa2c5619f57cbebed336930a452816a67fd35 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Tue, 25 Aug 2026 07:08:31 +0700 Subject: [PATCH 01/29] Stabilize `mem::conjure_zst` Tracking issue: https://github.com/rust-lang/rust/issues/95383 --- library/core/src/mem/mod.rs | 6 +++--- library/proc_macro/src/lib.rs | 2 +- tests/ui/consts/std/conjure_zst.rs | 2 -- tests/ui/consts/std/conjure_zst.stderr | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 26390e5071c53..1f55f27a7d5bd 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1692,7 +1692,6 @@ pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) { /// # Examples /// /// ``` -/// #![feature(mem_conjure_zst)] /// use std::mem::conjure_zst; /// /// assert_eq!(unsafe { conjure_zst::<()>() }, ()); @@ -1700,8 +1699,9 @@ pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) { /// ``` /// /// [inhabited]: https://doc.rust-lang.org/reference/glossary.html#inhabited -#[unstable(feature = "mem_conjure_zst", issue = "95383")] -#[rustc_const_unstable(feature = "mem_conjure_zst", issue = "95383")] +#[stable(feature = "mem_conjure_zst", since = "CURRENT_RUSTC_VERSION")] +#[rustc_const_stable(feature = "mem_conjure_zst", since = "CURRENT_RUSTC_VERSION")] +#[rustc_allow_const_fn_unstable(const_type_name)] // type_name() called only at run time pub const unsafe fn conjure_zst() -> T { const_assert!( T::IS_ZST, diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 2f026fb81ee1b..2d771f0890517 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -27,8 +27,8 @@ #![feature(restricted_std)] #![feature(rustc_attrs)] #![feature(extend_one)] -#![feature(mem_conjure_zst)] #![feature(f16)] +#![cfg_attr(bootstrap, feature(mem_conjure_zst))] #![recursion_limit = "256"] #![allow(internal_features)] #![deny(ffi_unwind_calls)] diff --git a/tests/ui/consts/std/conjure_zst.rs b/tests/ui/consts/std/conjure_zst.rs index c04deae502b0f..be52a5c4014b2 100644 --- a/tests/ui/consts/std/conjure_zst.rs +++ b/tests/ui/consts/std/conjure_zst.rs @@ -1,5 +1,3 @@ -#![feature(mem_conjure_zst)] - use std::{convert::Infallible, mem}; const INVALID: Infallible = unsafe { mem::conjure_zst() }; diff --git a/tests/ui/consts/std/conjure_zst.stderr b/tests/ui/consts/std/conjure_zst.stderr index d4c9df07a393a..8e5bedf0a3c04 100644 --- a/tests/ui/consts/std/conjure_zst.stderr +++ b/tests/ui/consts/std/conjure_zst.stderr @@ -1,5 +1,5 @@ error[E0080]: evaluation panicked: aborted execution: attempted to instantiate uninhabited type `!` - --> $DIR/conjure_zst.rs:5:38 + --> $DIR/conjure_zst.rs:3:38 | LL | const INVALID: Infallible = unsafe { mem::conjure_zst() }; | ^^^^^^^^^^^^^^^^^^ evaluation of `INVALID` failed inside this call From 66f69fc2ffe2691a3b97e99d86908f5ee5d86b67 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Thu, 17 Sep 2026 01:04:17 -0400 Subject: [PATCH 02/29] No longer auto-inline `f16` and `f128` on well-supported platforms In 5d818914af27 ("Always inline functions signatures containing `f16` or `f128`"), these types were changed to automatically inline so codegen wouldn't crash on poorly-supported platforms. We have since gained a cfg to reflect the type's codegen reliability. Update so we only check and auto-inline based on type if this config is set, which makes `f16` and `f128` act more like any other type on most platforms. We still can't remove this entirely since a lot of API in `std` wouldn't get inlined and would crash the few remaining poorly-supported backend+target combinations. --- .../src/cross_crate_inline.rs | 22 +++-- tests/codegen-llvm/float/f16-f128-inline.rs | 91 +++++++++++++++---- 2 files changed, 87 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_mir_transform/src/cross_crate_inline.rs b/compiler/rustc_mir_transform/src/cross_crate_inline.rs index 8a4e7740650fc..ca47b27d142a2 100644 --- a/compiler/rustc_mir_transform/src/cross_crate_inline.rs +++ b/compiler/rustc_mir_transform/src/cross_crate_inline.rs @@ -7,7 +7,7 @@ use rustc_middle::mir::*; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_session::config::{InliningThreshold, OptLevel}; -use rustc_span::bug; +use rustc_span::{bug, sym}; use crate::{inline, pass_manager as pm}; @@ -64,13 +64,19 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { return true; } - let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); - for ty in sig.inputs().skip_binder().iter().chain(std::iter::once(&sig.output().skip_binder())) - { - // FIXME(f16_f128): in order to avoid crashes building `core`, always inline to skip - // codegen if the function is not used. - if ty == &tcx.types.f16 || ty == &tcx.types.f128 { - return true; + let reliable_f16 = tcx.sess.config.contains(&(sym::target_has_reliable_f16, None)); + let reliable_f128 = tcx.sess.config.contains(&(sym::target_has_reliable_f128, None)); + if !reliable_f16 || !reliable_f128 { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + for ty in + sig.inputs().skip_binder().iter().chain(std::iter::once(&sig.output().skip_binder())) + { + // FIXME(f16,f128): in order to avoid crashes building `core`, inline on targets that + // have issues to skip codegen if the function is not used. + if (!reliable_f16 && ty == &tcx.types.f16) || (!reliable_f128 && ty == &tcx.types.f128) + { + return true; + } } } diff --git a/tests/codegen-llvm/float/f16-f128-inline.rs b/tests/codegen-llvm/float/f16-f128-inline.rs index aa2c38c209e86..592204cf000a1 100644 --- a/tests/codegen-llvm/float/f16-f128-inline.rs +++ b/tests/codegen-llvm/float/f16-f128-inline.rs @@ -1,29 +1,84 @@ -//@ revisions: default nopt -//@[nopt] compile-flags: -Copt-level=0 -Zcross-crate-inline-threshold=never -Zmir-opt-level=0 -Cno-prepopulate-passes +//@ revisions: DEFAULT NOPT +//@[NOPT] compile-flags: -Copt-level=0 -Zcross-crate-inline-threshold=never -Zmir-opt-level=0 -Cno-prepopulate-passes -// Ensure that functions using `f16` and `f128` are always inlined to avoid crashes -// when the backend does not support these types. +// Ensure that functions using `f16` and `f128` are always inlined when the backend does not +// support these types, to avoid crashes. #![crate_type = "lib"] #![feature(f128)] #![feature(f16)] +#![feature(cfg_target_has_reliable_f16_f128)] -pub fn f16_arg(_a: f16) { - // CHECK-NOT: f16_arg - todo!() -} +// This test does some tricky things. On `target_has_reliable_*` platforms: +// +// * `*_on_reliable` functions should always show up in codegen since they are not auto-inlined +// (the default), +// * `*_on_not_reliable` functions aren't defined at all, so `CHECK-NOT` passes. +// +// On non-`target_has_reliable_*` platforms: +// +// * `*_on_reliable` functions are dummies so they always show up in codegen. +// * `*_on_not_reliable` functions should be auto-inlined and thus not show up in codegen. +// +// `*_on_reliable` is only checked with NOPT since otherwise they may hit auto-inlining thresholds +// unrelated to the type signature. -pub fn f16_ret() -> f16 { - // CHECK-NOT: f16_ret - todo!() -} +// NOPT: f16_arg_on_reliable +// NOPT: f16_ret_on_reliable +// CHECK-NOT: f16_arg_on_not_reliable +// CHECK-NOT: f16_ret_on_not_reliable +cfg_select! { + target_has_reliable_f16 => { + pub fn f16_arg_on_reliable(_a: f16) { + todo!() + } + + pub fn f16_ret_on_reliable() -> f16 { + todo!() + } + } + _ => { + pub fn f16_arg_on_not_reliable(_a: f16) { + todo!() + } + + pub fn f16_ret_on_not_reliable() -> f16 { + todo!() + } -pub fn f128_arg(_a: f128) { - // CHECK-NOT: f128_arg - todo!() + #[unsafe(no_mangle)] + pub fn f16_arg_on_reliable() {} + #[unsafe(no_mangle)] + pub fn f16_ret_on_reliable() {} + } } -pub fn f128_ret() -> f128 { - // CHECK-NOT: f128_ret - todo!() +// NOPT: f128_arg_on_reliable +// NOPT: f128_ret_on_reliable +// CHECK-NOT: f128_arg_on_not_reliable +// CHECK-NOT: f128_ret_on_not_reliable +cfg_select! { + target_has_reliable_f128 => { + pub fn f128_arg_on_reliable(_a: f128) { + todo!() + } + + pub fn f128_ret_on_reliable() -> f128 { + todo!() + } + } + _ => { + pub fn f128_arg_on_not_reliable(_a: f128) { + todo!() + } + + pub fn f128_ret_on_not_reliable() -> f128 { + todo!() + } + + #[unsafe(no_mangle)] + pub fn f128_arg_on_reliable() {} + #[unsafe(no_mangle)] + pub fn f128_ret_on_reliable() {} + } } From b84b0bb827416d916e34b67cc55b9acab808958b Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Mon, 21 Sep 2026 11:54:29 +0330 Subject: [PATCH 03/29] Add rustdoc regression test for glob import of a crate that re-exports at its root --- .../auxiliary/glob-of-reexports-53724.rs | 13 +++++++++++++ .../inline_cross/glob-of-reexports-53724.rs | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/rustdoc-html/inline_cross/auxiliary/glob-of-reexports-53724.rs create mode 100644 tests/rustdoc-html/inline_cross/glob-of-reexports-53724.rs diff --git a/tests/rustdoc-html/inline_cross/auxiliary/glob-of-reexports-53724.rs b/tests/rustdoc-html/inline_cross/auxiliary/glob-of-reexports-53724.rs new file mode 100644 index 0000000000000..9adc1bd772064 --- /dev/null +++ b/tests/rustdoc-html/inline_cross/auxiliary/glob-of-reexports-53724.rs @@ -0,0 +1,13 @@ +#![crate_name = "inner"] + +pub mod ser { + pub trait Serialize {} + pub trait Serializer {} +} + +pub mod de { + pub trait Deserialize {} +} + +pub use ser::{Serialize, Serializer}; +pub use de::*; diff --git a/tests/rustdoc-html/inline_cross/glob-of-reexports-53724.rs b/tests/rustdoc-html/inline_cross/glob-of-reexports-53724.rs new file mode 100644 index 0000000000000..d80b4e78f96e2 --- /dev/null +++ b/tests/rustdoc-html/inline_cross/glob-of-reexports-53724.rs @@ -0,0 +1,17 @@ +// Regression test for . + +//@ aux-build:glob-of-reexports-53724.rs +//@ build-aux-docs +//@ ignore-cross-compile + +#![crate_name = "foo"] + +extern crate inner; + +//@ has foo/trait.Serialize.html +//@ has foo/trait.Serializer.html +//@ has foo/trait.Deserialize.html +//@ has foo/index.html '//a[@class="trait"]' 'Serialize' +//@ has foo/index.html '//a[@class="trait"]' 'Serializer' +//@ has foo/index.html '//a[@class="trait"]' 'Deserialize' +pub use inner::*; From 93bfeed513279837781482ce88707cb6a18ef8c1 Mon Sep 17 00:00:00 2001 From: Yukang Date: Tue, 22 Sep 2026 00:18:16 +0800 Subject: [PATCH 04/29] Add regression tests for Option predicate suggestion precedence --- tests/ui/suggestions/option-to-bool.rs | 29 ++++++++++++++- tests/ui/suggestions/option-to-bool.stderr | 43 +++++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/tests/ui/suggestions/option-to-bool.rs b/tests/ui/suggestions/option-to-bool.rs index bbc5d1d71cb72..b36c28c06ef9d 100644 --- a/tests/ui/suggestions/option-to-bool.rs +++ b/tests/ui/suggestions/option-to-bool.rs @@ -1,7 +1,34 @@ +// An `is_some()` suggestion must apply to the whole expression, preserving its precedence. + fn foo(x: Option) { if true && x {} //~^ ERROR mismatched types //~| HELP use `Option::is_some` to test if the `Option` has a value + + let reference = &x; + if *reference {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value + + if &x {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value + + if Value + Value {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value } -fn main() {} +struct Value; + +impl std::ops::Add for Value { + type Output = Option; + + fn add(self, _: Self) -> Self::Output { + Some(1) + } +} + +fn main() { + foo(Some(1)); +} diff --git a/tests/ui/suggestions/option-to-bool.stderr b/tests/ui/suggestions/option-to-bool.stderr index ab97eae6c5c55..2ee282670d794 100644 --- a/tests/ui/suggestions/option-to-bool.stderr +++ b/tests/ui/suggestions/option-to-bool.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/option-to-bool.rs:2:16 + --> $DIR/option-to-bool.rs:4:16 | LL | if true && x {} | ---- ^ expected `bool`, found `Option` @@ -13,6 +13,45 @@ help: use `Option::is_some` to test if the `Option` has a value LL | if true && x.is_some() {} | ++++++++++ -error: aborting due to 1 previous error +error[E0308]: mismatched types + --> $DIR/option-to-bool.rs:9:8 + | +LL | if *reference {} + | ^^^^^^^^^^ expected `bool`, found `Option` + | + = note: expected type `bool` + found enum `Option` +help: use `Option::is_some` to test if the `Option` has a value + | +LL | if *reference.is_some() {} + | ++++++++++ + +error[E0308]: mismatched types + --> $DIR/option-to-bool.rs:13:8 + | +LL | if &x {} + | ^^ expected `bool`, found `&Option` + | + = note: expected type `bool` + found reference `&Option` +help: use `Option::is_some` to test if the `Option` has a value + | +LL | if &x.is_some() {} + | ++++++++++ + +error[E0308]: mismatched types + --> $DIR/option-to-bool.rs:17:8 + | +LL | if Value + Value {} + | ^^^^^^^^^^^^^ expected `bool`, found `Option` + | + = note: expected type `bool` + found enum `Option` +help: use `Option::is_some` to test if the `Option` has a value + | +LL | if Value + Value.is_some() {} + | ++++++++++ + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0308`. From c9412ec1c0873c1ae551ebcf7b9d3ceb077aec9a Mon Sep 17 00:00:00 2001 From: Yukang Date: Wed, 23 Sep 2026 21:32:00 +0800 Subject: [PATCH 05/29] Fix suggestion for Option to bool with proper precedence handling --- .../src/fn_ctxt/suggestions.rs | 14 +++++--- tests/ui/suggestions/option-to-bool.fixed | 35 +++++++++++++++++++ tests/ui/suggestions/option-to-bool.rs | 1 + tests/ui/suggestions/option-to-bool.stderr | 20 +++++------ 4 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 tests/ui/suggestions/option-to-bool.fixed diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 7f8edae4f17c3..62d48af2f3f08 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -1692,12 +1692,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let suggestion = match self.tcx.hir_maybe_get_struct_pattern_shorthand_field(expr) { - Some(ident) => format!(": {ident}.is_some()"), - None => ".is_some()".to_string(), + Some(ident) => vec![(expr.span.shrink_to_hi(), format!(": {ident}.is_some()"))], + None if self.precedence(expr) < ExprPrecedence::Unambiguous => { + // Apply the method to the whole expression, e.g. `(*value).is_some()`. + vec![ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), ").is_some()".to_string()), + ] + } + None => vec![(expr.span.shrink_to_hi(), ".is_some()".to_string())], }; - diag.span_suggestion_verbose( - expr.span.shrink_to_hi(), + diag.multipart_suggestion( "use `Option::is_some` to test if the `Option` has a value", suggestion, Applicability::MachineApplicable, diff --git a/tests/ui/suggestions/option-to-bool.fixed b/tests/ui/suggestions/option-to-bool.fixed new file mode 100644 index 0000000000000..b6160a1bb8f15 --- /dev/null +++ b/tests/ui/suggestions/option-to-bool.fixed @@ -0,0 +1,35 @@ +// An `is_some()` suggestion must apply to the whole expression, preserving its precedence. +//@ run-rustfix + +fn foo(x: Option) { + if true && x.is_some() {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value + + let reference = &x; + if (*reference).is_some() {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value + + if (&x).is_some() {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value + + if (Value + Value).is_some() {} + //~^ ERROR mismatched types + //~| HELP use `Option::is_some` to test if the `Option` has a value +} + +struct Value; + +impl std::ops::Add for Value { + type Output = Option; + + fn add(self, _: Self) -> Self::Output { + Some(1) + } +} + +fn main() { + foo(Some(1)); +} diff --git a/tests/ui/suggestions/option-to-bool.rs b/tests/ui/suggestions/option-to-bool.rs index b36c28c06ef9d..25272b96dfc28 100644 --- a/tests/ui/suggestions/option-to-bool.rs +++ b/tests/ui/suggestions/option-to-bool.rs @@ -1,4 +1,5 @@ // An `is_some()` suggestion must apply to the whole expression, preserving its precedence. +//@ run-rustfix fn foo(x: Option) { if true && x {} diff --git a/tests/ui/suggestions/option-to-bool.stderr b/tests/ui/suggestions/option-to-bool.stderr index 2ee282670d794..dd49b1b2b0ad1 100644 --- a/tests/ui/suggestions/option-to-bool.stderr +++ b/tests/ui/suggestions/option-to-bool.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/option-to-bool.rs:4:16 + --> $DIR/option-to-bool.rs:5:16 | LL | if true && x {} | ---- ^ expected `bool`, found `Option` @@ -14,7 +14,7 @@ LL | if true && x.is_some() {} | ++++++++++ error[E0308]: mismatched types - --> $DIR/option-to-bool.rs:9:8 + --> $DIR/option-to-bool.rs:10:8 | LL | if *reference {} | ^^^^^^^^^^ expected `bool`, found `Option` @@ -23,11 +23,11 @@ LL | if *reference {} found enum `Option` help: use `Option::is_some` to test if the `Option` has a value | -LL | if *reference.is_some() {} - | ++++++++++ +LL | if (*reference).is_some() {} + | + +++++++++++ error[E0308]: mismatched types - --> $DIR/option-to-bool.rs:13:8 + --> $DIR/option-to-bool.rs:14:8 | LL | if &x {} | ^^ expected `bool`, found `&Option` @@ -36,11 +36,11 @@ LL | if &x {} found reference `&Option` help: use `Option::is_some` to test if the `Option` has a value | -LL | if &x.is_some() {} - | ++++++++++ +LL | if (&x).is_some() {} + | + +++++++++++ error[E0308]: mismatched types - --> $DIR/option-to-bool.rs:17:8 + --> $DIR/option-to-bool.rs:18:8 | LL | if Value + Value {} | ^^^^^^^^^^^^^ expected `bool`, found `Option` @@ -49,8 +49,8 @@ LL | if Value + Value {} found enum `Option` help: use `Option::is_some` to test if the `Option` has a value | -LL | if Value + Value.is_some() {} - | ++++++++++ +LL | if (Value + Value).is_some() {} + | + +++++++++++ error: aborting due to 4 previous errors From e01dc94337d6873e5c7df8afa4b3fd6ab97aca25 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 18 Sep 2026 09:40:36 +1000 Subject: [PATCH 06/29] Some `emit_diagnostic` improvements There is one match arm handling some `ForceWarning` cases and another arm handling the remaining `ForceWarning` case and also `Expect`. They can be rearranged into one arm handling `ForceWarning` and one arm handling `Expect`. Also fix some comments: - `ForceWarning` no longer has a field. - Clarify the `LintExpectationId` location. - The one about deduplication was inverted. --- compiler/rustc_errors/src/lib.rs | 27 +++++++++++++++------------ compiler/rustc_middle/src/lint.rs | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 97b8557b95ef6..df9ce5f0673b8 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1258,7 +1258,11 @@ impl DiagCtxtInner { }; } } - ForceWarning if diagnostic.lint_id.is_none() => {} // `ForceWarning(Some(...))` is below, with `Expect` + ForceWarning => { + if let Some(lint_id) = diagnostic.lint_id { + self.fulfilled_expectations.insert(lint_id); + } + } Warning => { if !self.flags.can_emit_warnings { // We are not emitting warnings. @@ -1279,14 +1283,12 @@ impl DiagCtxtInner { } return None; } - Expect | ForceWarning => { + Expect => { self.fulfilled_expectations.insert(diagnostic.lint_id.unwrap()); - if let Expect = diagnostic.level { - // Nothing emitted here for expected lints. - TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); - self.suppressed_expected_diag = true; - return None; - } + // Nothing emitted here for expected lints. + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + self.suppressed_expected_diag = true; + return None; } } @@ -1313,7 +1315,7 @@ impl DiagCtxtInner { ) && mem::replace(&mut self.emitted_recursion_depth_exceeding_limit, true) }); - // Only emit the diagnostic if we've been asked to deduplicate or + // Only emit the diagnostic if deduplication is disabled or we // haven't already emitted an equivalent diagnostic. if !silence_recursion_depth_exceeded_limit && !(self.flags.deduplicate_diagnostics && already_emitted) @@ -1580,8 +1582,8 @@ pub enum Level { /// A `force-warn` lint warning about the code being compiled. Does not prevent compilation /// from finishing. /// - /// Requires a [`LintExpectationId`] for expected lint diagnostics. In all other cases this - /// should be `None`. + /// Requires the corresponding `DiagInner::lint_id` to be `Some` for expected lint diagnostics, + /// and `None` in all other cases. ForceWarning, /// A warning about the code being compiled. Does not prevent compilation from finishing. @@ -1605,7 +1607,8 @@ pub enum Level { /// Only used for lints. Allow, - /// Only used for lints. Requires a [`LintExpectationId`] for silencing the lints. + /// Only used for lints. Requires the corresponding `DiagInner::lint_id` to be `Some` to + /// silence the lints. Expect, } diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 820375ada47a5..5d631acc1b528 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -477,7 +477,7 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>( // emitted or we'll get a `must_produce_diag` ICE. // // When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors: - // 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning(_)` + // 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning` // or `Error`, then the diagnostic will be emitted regardless of CLI options. // 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by // `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic From 4fc112e765339c75e13bfc8b849b2a4274a90aaf Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 22 Sep 2026 12:48:55 +1000 Subject: [PATCH 07/29] Remove an out-of-date FIXME comment I think the ship has sailed on the "every error should have an error code" idea and it's not worth pretending otherwise. --- compiler/rustc_errors/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index df9ce5f0673b8..33db5577baeb5 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1018,7 +1018,6 @@ impl<'a> DiagCtxtHandle<'a> { self.create_fatal(fatal).emit_fatal() } - // FIXME: This method should be removed (every error should have an associated error code). #[track_caller] pub fn struct_err(self, msg: impl Into) -> Diag<'a> { Diag::new(self, Error, msg) From e10ba5d3d9f2879f5b47e66659792ad179d5ff1f Mon Sep 17 00:00:00 2001 From: Roland Xu Date: Fri, 25 Sep 2026 10:21:45 +0800 Subject: [PATCH 08/29] Add test for issue 163293 --- .../pub-reexport-extern-std-issue-163293.rs | 5 +++ ...ub-reexport-extern-std-issue-163293.stderr | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/ui/pub/pub-reexport-extern-std-issue-163293.rs create mode 100644 tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr diff --git a/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs b/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs new file mode 100644 index 0000000000000..ad3a07bbd5f07 --- /dev/null +++ b/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs @@ -0,0 +1,5 @@ +pub use std as x; + +//@ edition: 2015 + +fn main() {} diff --git a/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr b/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr new file mode 100644 index 0000000000000..880a871601e55 --- /dev/null +++ b/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr @@ -0,0 +1,32 @@ +error[E0365]: extern crate `std` is private and cannot be re-exported + --> $DIR/pub-reexport-extern-std-issue-163293.rs:1:9 + | +LL | pub use std as x; + | ^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #127909 + = note: `#[deny(pub_use_of_private_extern_crate)]` (part of `#[deny(future_incompatible)]`) on by default +help: consider making the `extern crate` item publicly accessible + | +LL | pub pub use std as x; + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0365`. +Future incompatibility report: Future breakage diagnostic: +error[E0365]: extern crate `std` is private and cannot be re-exported + --> $DIR/pub-reexport-extern-std-issue-163293.rs:1:9 + | +LL | pub use std as x; + | ^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #127909 + = note: `#[deny(pub_use_of_private_extern_crate)]` (part of `#[deny(future_incompatible)]`) on by default +help: consider making the `extern crate` item publicly accessible + | +LL | pub pub use std as x; + | +++ + From b10ccb8e2e3e3ddcd2fcc92d97c8ade0814e488e Mon Sep 17 00:00:00 2001 From: Roland Xu Date: Fri, 25 Sep 2026 10:35:19 +0800 Subject: [PATCH 09/29] Don't emit sugg for pub-reexport extern std --- compiler/rustc_resolve/src/diagnostics/mod.rs | 2 +- compiler/rustc_resolve/src/imports.rs | 7 +++++-- tests/ui/pub/pub-reexport-extern-std-issue-163293.rs | 2 ++ tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr | 8 -------- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index d17e01425d2c9..38981426837c4 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -840,7 +840,7 @@ pub(crate) struct PrivateExternCrateReexport { style = "verbose", applicability = "maybe-incorrect" )] - pub sugg: Span, + pub sugg: Option, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 938a6cb29032f..5cd45c5de5230 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1673,8 +1673,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(extern_crate_id) = pub_use_of_private_extern_crate_hack(import.summary(), decl) { let ImportKind::Single { id, .. } = import.kind else { unreachable!() }; - let sugg = self.tcx.source_span(extern_crate_id).shrink_to_lo(); - let diagnostic = crate::diagnostics::PrivateExternCrateReexport { ident, sugg }; + let sugg = self.tcx.source_span(extern_crate_id); + let diagnostic = crate::diagnostics::PrivateExternCrateReexport { + ident, + sugg: sugg.can_be_used_for_suggestions().then(|| sugg.shrink_to_lo()), + }; return Some(BufferedEarlyLint { lint_id: LintId::of(PUB_USE_OF_PRIVATE_EXTERN_CRATE), node_id: id, diff --git a/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs b/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs index ad3a07bbd5f07..9189ca2fb41b7 100644 --- a/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs +++ b/tests/ui/pub/pub-reexport-extern-std-issue-163293.rs @@ -1,4 +1,6 @@ pub use std as x; +//~^ ERROR extern crate `std` is private and cannot be re-exported +//~^^ WARN this was previously accepted by the compiler but is being phased out //@ edition: 2015 diff --git a/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr b/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr index 880a871601e55..ee5ba1fc80aa9 100644 --- a/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr +++ b/tests/ui/pub/pub-reexport-extern-std-issue-163293.stderr @@ -7,10 +7,6 @@ LL | pub use std as x; = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #127909 = note: `#[deny(pub_use_of_private_extern_crate)]` (part of `#[deny(future_incompatible)]`) on by default -help: consider making the `extern crate` item publicly accessible - | -LL | pub pub use std as x; - | +++ error: aborting due to 1 previous error @@ -25,8 +21,4 @@ LL | pub use std as x; = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #127909 = note: `#[deny(pub_use_of_private_extern_crate)]` (part of `#[deny(future_incompatible)]`) on by default -help: consider making the `extern crate` item publicly accessible - | -LL | pub pub use std as x; - | +++ From 5237e38fddf1699ea6186400c17c0a32b71fb836 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Thu, 24 Sep 2026 17:48:28 +0300 Subject: [PATCH 10/29] Support -Z merge-functions with gcc and add stack-protector asm tests This is a part of the stack-protector work. Adding support for merge-functions is not strictly needed, but it does prevent breakage if the GCC function merging logic changes. Not adding tests for -Z merge-functions since I don't see any such tests for the LLVM side of it. --- compiler/rustc_codegen_gcc/src/gcc_util.rs | 11 +- compiler/rustc_codegen_llvm/src/llvm_util.rs | 2 +- compiler/rustc_codegen_ssa/src/back/write.rs | 7 +- compiler/rustc_session/src/options.rs | 1 + compiler/rustc_session/src/session.rs | 10 +- src/bootstrap/src/core/build_steps/test.rs | 6 + .../builder/cli_paths/snapshots/x_test.snap | 3 + .../snapshots/x_test_skip_coverage.snap | 3 + .../snapshots/x_test_skip_coverage_map.snap | 3 + .../snapshots/x_test_skip_coverage_run.snap | 3 + .../snapshots/x_test_skip_tests_coverage.snap | 3 + .../cli_paths/snapshots/x_test_tests.snap | 3 + .../snapshots/x_test_tests_skip_coverage.snap | 3 + src/bootstrap/src/core/builder/mod.rs | 1 + src/bootstrap/src/core/builder/tests.rs | 2 + src/tools/compiletest/src/common.rs | 1 + src/tools/compiletest/src/runtest.rs | 3 +- .../stack-protector-heuristics-effect.rs | 331 ++++++++++++++++++ 18 files changed, 384 insertions(+), 12 deletions(-) create mode 100644 tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 0628171e488b3..4850a5fc5ca31 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -9,7 +9,9 @@ use rustc_codegen_ssa::target_features; use rustc_data_structures::smallvec::{SmallVec, smallvec}; use rustc_session::config::NATIVE_CPU; use rustc_session::{EarlySession, Session}; -use rustc_target::spec::{Arch, RelocModel, StackProbeType, StackProtector, Target}; +use rustc_target::spec::{ + Arch, MergeFunctions, RelocModel, StackProbeType, StackProtector, Target, +}; fn gcc_features_by_flags(sess: &EarlySession, features: &mut Vec) { target_features::retpoline_features_by_flags(sess, features); @@ -220,6 +222,13 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { StackProtector::None => (), } + match sess.merge_functions() { + MergeFunctions::Disabled | MergeFunctions::Trampolines => { + context.add_command_line_option("-fno-ipa-icf-functions"); + } + MergeFunctions::Aliases => {} + } + match sess.target.stack_probes { StackProbeType::None => (), StackProbeType::Inline | StackProbeType::InlineOrCall { .. } => { diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 9e37205f230ac..9cfa74ffd019d 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -120,7 +120,7 @@ unsafe fn configure_llvm(sess: &EarlySession) { add("-generate-arange-section", false); } - match sess.opts.unstable_opts.merge_functions.unwrap_or(sess.target.merge_functions) { + match sess.merge_functions() { MergeFunctions::Disabled | MergeFunctions::Trampolines => {} MergeFunctions::Aliases => { add("-mergefunc-use-aliases", false); diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 147e4c43c7103..f87bfa34ddc8b 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -244,12 +244,7 @@ impl ModuleConfig { // backends (again, NVPTX). Therefore, allow targets to opt out of // the MergeFunctions pass, but otherwise keep the pass enabled (at // O2 and O3) since it can be useful for reducing code size. - merge_functions: match sess - .opts - .unstable_opts - .merge_functions - .unwrap_or(sess.target.merge_functions) - { + merge_functions: match sess.merge_functions() { MergeFunctions::Disabled => false, MergeFunctions::Trampolines | MergeFunctions::Aliases => { use config::OptLevel::*; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 13fc598d86bfa..61b7d792d9a08 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2643,6 +2643,7 @@ options! { maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED], "save as much information as possible about the correspondence between MIR and HIR \ as source scopes (default: no)"), + #[rustc_lint_opt_deny_field_access("use `Session::merge_functions` instead of this field")] merge_functions: Option = (None, parse_merge_functions, [TRACKED], "control the operation of the MergeFunctions LLVM pass, taking \ the same values as the target option of the same name"), diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 3b60a13b8e6f2..060e614e82180 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -28,9 +28,9 @@ use rustc_span::{RealFileName, Span, Symbol}; use rustc_structures::{CrateType, Limit}; use rustc_target::asm::InlineAsmArch; use rustc_target::spec::{ - Arch, CfgAbi, CodeModel, DebuginfoKind, Os, PanicStrategy, RelocModel, RelroLevel, - SanitizerSet, SmallDataThresholdSupport, SplitDebuginfo, StackProtector, SymbolVisibility, - Target, TargetTuple, TlsModel, apple, + Arch, CfgAbi, CodeModel, DebuginfoKind, MergeFunctions, Os, PanicStrategy, RelocModel, + RelroLevel, SanitizerSet, SmallDataThresholdSupport, SplitDebuginfo, StackProtector, + SymbolVisibility, Target, TargetTuple, TlsModel, apple, }; use crate::code_stats::CodeStats; @@ -423,6 +423,10 @@ impl EarlySession { .sanitizer .combine_with_defaults(self.target.options.default_sanitizers) } + + pub fn merge_functions(&self) -> MergeFunctions { + self.opts.unstable_opts.merge_functions.unwrap_or(self.target.merge_functions) + } } /// Some info about the backend, returned by `CodegenBackend::init` and put into the `Session`. diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index cd647eb32f515..3006fddbbe662 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -2040,6 +2040,12 @@ test!(BuildStd { default: false }); +test!(AssemblyGcc { + path: "tests/assembly-gcc", + mode: CompiletestMode::Assembly, + suite: "assembly-gcc", + default: true +}); test!(AssemblyLlvm { path: "tests/assembly-llvm", mode: CompiletestMode::Assembly, diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test.snap index 32ed54050dc3d..787686b117cd2 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test.snap @@ -23,6 +23,9 @@ expression: test [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage.snap index d1cdf81a14e08..879114ffc8170 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage.snap @@ -20,6 +20,9 @@ expression: test --skip=coverage [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_map.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_map.snap index df4b8a2ce1299..a30e3a2407937 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_map.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_map.snap @@ -23,6 +23,9 @@ expression: test --skip=coverage-map [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_run.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_run.snap index 75992df8aa616..12e0a3233f7ea 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_run.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_coverage_run.snap @@ -23,6 +23,9 @@ expression: test --skip=coverage-run [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_tests_coverage.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_tests_coverage.snap index a44052fac41f9..3b07718b8aecf 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_tests_coverage.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_skip_tests_coverage.snap @@ -20,6 +20,9 @@ expression: test --skip=tests/coverage [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests.snap index c0105510ae416..6db9443b8543c 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests.snap @@ -20,6 +20,9 @@ expression: test tests [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests_skip_coverage.snap b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests_skip_coverage.snap index d1a90ca426918..ab4e9e6f7d997 100644 --- a/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests_skip_coverage.snap +++ b/src/bootstrap/src/core/builder/cli_paths/snapshots/x_test_tests_skip_coverage.snap @@ -17,6 +17,9 @@ expression: test tests --skip=coverage [Test] test::CodegenUnits targets: [aarch64-unknown-linux-gnu] - Suite(tests/codegen-units) +[Test] test::AssemblyGcc + targets: [aarch64-unknown-linux-gnu] + - Suite(tests/assembly-gcc) [Test] test::AssemblyLlvm targets: [aarch64-unknown-linux-gnu] - Suite(tests/assembly-llvm) diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 0593f30946320..c221a976b2698 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -886,6 +886,7 @@ impl<'a> Builder<'a> { test::MirOpt, test::CodegenLlvm, test::CodegenUnits, + test::AssemblyGcc, test::AssemblyLlvm, test::Incremental, test::Debuginfo, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index cc6ac39672950..dd1beb65e7fe0 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1943,6 +1943,7 @@ mod snapshot { [test] compiletest-mir-opt 1 [test] compiletest-codegen-llvm 1 [test] compiletest-codegen-units 1 + [test] compiletest-assembly-gcc 1 [test] compiletest-assembly-llvm 1 [test] compiletest-incremental 1 [test] compiletest-debuginfo 1 @@ -2124,6 +2125,7 @@ mod snapshot { [test] compiletest-mir-opt 2 [test] compiletest-codegen-llvm 2 [test] compiletest-codegen-units 2 + [test] compiletest-assembly-gcc 2 [test] compiletest-assembly-llvm 2 [test] compiletest-incremental 2 [test] compiletest-debuginfo 2 diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 8c580d3520dfd..1968f5eb73d4e 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -60,6 +60,7 @@ impl TestMode { string_enum! { #[derive(Clone, Copy, PartialEq, Debug)] pub(crate) enum TestSuite { + AssemblyGcc => "assembly-gcc", AssemblyLlvm => "assembly-llvm", CodegenLlvm => "codegen-llvm", CodegenUnits => "codegen-units", diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index c728e56cf639d..0bb8a7003589c 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1604,7 +1604,8 @@ impl<'test> TestCx<'test> { // Exhaustively match all other suites. // Note that some suites never actually use this method, so the // return value for those suites is not necessarily meaningful. - TestSuite::AssemblyLlvm + TestSuite::AssemblyGcc + | TestSuite::AssemblyLlvm | TestSuite::BuildStd | TestSuite::CodegenLlvm | TestSuite::CodegenUnits diff --git a/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs new file mode 100644 index 0000000000000..f9ddea2afedbd --- /dev/null +++ b/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs @@ -0,0 +1,331 @@ +//@ revisions: all strong basic none missing +//@ assembly-output: emit-asm +//@ ignore-apple slightly different policy on stack protection of arrays +//@ ignore-msvc stack check code uses different function names +//@ ignore-nvptx64 stack protector is not supported +//@ ignore-wasm32-unknown-unknown +//@ [all] compile-flags: -Z stack-protector=all +//@ [strong] compile-flags: -Z stack-protector=strong +//@ [basic] compile-flags: -Z stack-protector=basic +//@ [none] compile-flags: -Z stack-protector=none +//@ compile-flags: -C opt-level=2 -Z merge-functions=disabled + +#![crate_type = "lib"] +#![allow(internal_features)] +#![feature(unsized_fn_params)] + +// CHECK-LABEL: "emptyfn" +#[no_mangle] +pub fn emptyfn() { + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "array_char" +#[no_mangle] +pub fn array_char(f: fn(*const char)) { + let a = ['c'; 1]; + let b = ['d'; 3]; + let c = ['e'; 15]; + + f(&a as *const _); + f(&b as *const _); + f(&c as *const _); + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "array_u8_1" +#[no_mangle] +pub fn array_u8_1(f: fn(*const u8)) { + let a = [0u8; 1]; + f(&a as *const _); + + // The 'strong' heuristic adds stack protection to functions with local + // array variables regardless of their size. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "array_u8_small" +#[no_mangle] +pub fn array_u8_small(f: fn(*const u8)) { + let a = [0u8; 2]; + let b = [0u8; 7]; + f(&a as *const _); + f(&b as *const _); + + // Small arrays do not lead to stack protection by the 'basic' heuristic. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "array_u8_large" +#[no_mangle] +pub fn array_u8_large(f: fn(*const u8)) { + let a = [0u8; 9]; + f(&a as *const _); + + // Since `a` is a byte array with size greater than 8, the basic heuristic + // will also protect this function. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +#[derive(Copy, Clone)] +pub struct ByteSizedNewtype(u8); + +// CHECK-LABEL: "array_bytesizednewtype_9" +#[no_mangle] +pub fn array_bytesizednewtype_9(f: fn(*const ByteSizedNewtype)) { + let a = [ByteSizedNewtype(0); 9]; + f(&a as *const _); + + // Since `a` is a byte array in the GCC output, the basic heuristic will + // also protect this function. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "local_var_addr_used_indirectly" +#[no_mangle] +pub fn local_var_addr_used_indirectly(f: fn(bool)) { + let a = 5; + let a_addr = &a as *const _ as usize; + f(a_addr & 0x10 == 0); + + // This function takes the address of a local variable taken. Although this + // address is never used as a way to refer to stack memory, the `strong` + // heuristic adds stack smash protection. This is also the case in C++: + // ``` + // cat << EOF | g++ -O2 -fstack-protector-strong -S -x c++ - -o - | grep stack_chk + // #include + // void f(void (*g)(bool)) { + // int32_t x; + // g((reinterpret_cast(&x) & 0x10U) == 0); + // } + // EOF + // ``` + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "local_string_addr_taken" +#[no_mangle] +pub fn local_string_addr_taken(f: fn(&String)) { + let x = String::new(); + f(&x); + + // Taking the address of the local variable `x` leads to stack smash + // protection. It does not matter that the reference is not mut. + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +pub trait SelfByRef { + fn f(&self) -> i32; +} + +impl SelfByRef for i32 { + fn f(&self) -> i32 { + return self + 1; + } +} + +// CHECK-LABEL: "local_var_addr_taken_used_locally_only" +#[no_mangle] +pub fn local_var_addr_taken_used_locally_only(factory: fn() -> i32, sink: fn(i32)) { + let x = factory(); + let g = x.f(); + sink(g); + + // Even though the local variable conceptually has its address taken, as + // it's passed by reference to the trait function, the use of the reference + // is easily inlined. There is therefore no stack smash protection even with + // the `strong` heuristic. + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +pub struct Gigastruct { + does: u64, + not: u64, + have: u64, + array: u64, + members: u64, +} + +// CHECK-LABEL: "local_large_var_moved" +#[no_mangle] +pub fn local_large_var_moved(f: fn(Gigastruct)) { + let x = Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 }; + f(x); + + // Even though the local variable conceptually doesn't have its address + // taken, it's so large that the "move" is implemented with a reference to a + // stack-local variable in the ABI. Consequently, this function *is* + // protected. This is also the case for rvalue-references in C++, + // regardless of struct size: + // ``` + // cat < + // #include + // void f(void (*g)(uint64_t&&)) { + // uint64_t x; + // g(std::move(x)); + // } + // EOF + // ``` + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "local_large_var_cloned" +#[no_mangle] +pub fn local_large_var_cloned(f: fn(Gigastruct)) { + f(Gigastruct { does: 0, not: 1, have: 2, array: 3, members: 4 }); + + // A new instance of `Gigastruct` is passed to `f()`, without any apparent + // connection to this stack frame. Still, since instances of `Gigastruct` + // are sufficiently large, it is allocated in the caller stack frame and + // passed as a pointer. As such, this function is *also* protected, just + // like `local_large_var_moved`. + // + // This matches clang++ behavior, but not g++ behavior. + // + // In any case, both options are fine from a specification point of view, there + // is no "user-accessible pointer", and there is no strong reason to avoid generating + // a canary in this case, since it doesn't seem to be one of the performance-critical + // cases in which avoiding generating a canary is important. + // + // ``` + // cat < + // #include + // struct Gigastruct { uint64_t a, b, c, d, e; }; + // void f(void (*g)(Gigastruct)) { + // g(Gigastruct{}); + // } + // EOF + // ``` + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +extern "C" { + // Difference between LLVM and GCC: LLVM will not generate stack protection + // for "external" calls to alloca, but gcc will. See the matching test for + // stack-protector-heuristics-effect under assembly-llvm. + // + // This is a difference in heuristics and therefore fine. + // + // Check that rustc_codegen_gcc matches gcc behavior. + + fn alloca(size: usize) -> *mut (); +} + +// CHECK-LABEL: "alloca_small_compile_time_constant_arg" +#[no_mangle] +pub fn alloca_small_compile_time_constant_arg(f: fn(*mut ())) { + f(unsafe { alloca(8) }); + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "alloca_large_compile_time_constant_arg" +#[no_mangle] +pub fn alloca_large_compile_time_constant_arg(f: fn(*mut ())) { + f(unsafe { alloca(9) }); + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// CHECK-LABEL: "alloca_dynamic_arg" +#[no_mangle] +pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { + f(unsafe { alloca(n) }); + + // all: __stack_chk_fail + // strong: __stack_chk_fail + // basic: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} + +// The question then is: in what ways can Rust code generate array-`alloca` +// GCC instructions? This appears to only be generated by +// rustc_codegen_ssa::traits::Builder::array_alloca() through +// rustc_codegen_ssa::mir::operand::OperandValue::store_unsized(). FWICT +// this is support for the "unsized locals" unstable feature: +// https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html. + +// CHECK-LABEL: "unsized_fn_param" +#[no_mangle] +pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) { + let n = if l { 1 } else { 2 }; + f(*Box::<[u8]>::from(&s[0..n])); // slice-copy with Box::from + + // Even though slices are conceptually passed by-value both into this + // function and into `f()`, this is implemented with pass-by-reference + // using a suitably constructed fat-pointer (as if the functions + // accepted &[u8]). This function therefore doesn't need dynamic array + // alloca, and is therefore not protected by the `strong` or `basic` + // heuristics. + + // all: __stack_chk_fail + // strong-NOT: __stack_chk_fail + // basic-NOT: __stack_chk_fail + // none-NOT: __stack_chk_fail + // missing-NOT: __stack_chk_fail +} From c6dc51196d5d387cf44837e9d75212462fb07e4f Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Thu, 24 Sep 2026 22:01:39 +0300 Subject: [PATCH 11/29] address comments by bjorn3 --- compiler/rustc_codegen_gcc/src/gcc_util.rs | 7 ++-- compiler/rustc_target/src/spec/mod.rs | 7 ++++ .../stack-protector-heuristics-effect.rs | 32 +++---------------- .../stack-protector-heuristics-effect.rs | 29 ++--------------- 4 files changed, 20 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 4850a5fc5ca31..90d08d13a080a 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -223,10 +223,13 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { } match sess.merge_functions() { - MergeFunctions::Disabled | MergeFunctions::Trampolines => { + MergeFunctions::Disabled => { context.add_command_line_option("-fno-ipa-icf-functions"); } - MergeFunctions::Aliases => {} + // GCC always merges functions with trampolines rather than aliases, so we don't + // need to differentiate MergeFunctions::Trampolines and MergeFunctions::Aliases + // as trampolines are allowed either way. + MergeFunctions::Trampolines | MergeFunctions::Aliases => {} } match sess.target.stack_probes { diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 42d817c7daa31..2d7cd049eb61c 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -927,8 +927,15 @@ impl ToJson for SmallDataThresholdSupport { crate::target_spec_enum! { pub enum MergeFunctions { + /// Disable function merging entirely. Disabled = "disabled", + /// Allow function merging via trampolines (i.e., functions can be implemented + /// in terms of a jump to an unrelated function with identical behavior), but + /// not via object-format-level global aliases. Trampolines = "trampolines", + /// (Default) allow function merging via either trampolines or (object format + /// level) global aliases. LLVM will normally use global aliases for merging, but + /// trampolines are also permissible. Aliases = "aliases", } diff --git a/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs index f9ddea2afedbd..c8945ed96b3ee 100644 --- a/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs +++ b/tests/assembly-gcc/stack-protector/stack-protector-heuristics-effect.rs @@ -235,7 +235,8 @@ pub fn local_large_var_cloned(f: fn(Gigastruct)) { // In any case, both options are fine from a specification point of view, there // is no "user-accessible pointer", and there is no strong reason to avoid generating // a canary in this case, since it doesn't seem to be one of the performance-critical - // cases in which avoiding generating a canary is important. + // cases in which avoiding generating a canary is important, so it seems that + // rustc should keep the clang-like behavior of generating a canary here. // // ``` // cat <::from(&s[0..n])); // slice-copy with Box::from - - // Even though slices are conceptually passed by-value both into this - // function and into `f()`, this is implemented with pass-by-reference - // using a suitably constructed fat-pointer (as if the functions - // accepted &[u8]). This function therefore doesn't need dynamic array - // alloca, and is therefore not protected by the `strong` or `basic` - // heuristics. - - // all: __stack_chk_fail - // strong-NOT: __stack_chk_fail - // basic-NOT: __stack_chk_fail - // none-NOT: __stack_chk_fail - // missing-NOT: __stack_chk_fail -} +// rustc can currently (as of 1.98) not generate variable-sized allocas, except for +// variable-sized scalable vector types, so their interaction with stack-protector +// does not need to be tested. diff --git a/tests/assembly-llvm/stack-protector/stack-protector-heuristics-effect.rs b/tests/assembly-llvm/stack-protector/stack-protector-heuristics-effect.rs index 3728ff3adf105..56cf0035e25bf 100644 --- a/tests/assembly-llvm/stack-protector/stack-protector-heuristics-effect.rs +++ b/tests/assembly-llvm/stack-protector/stack-protector-heuristics-effect.rs @@ -317,29 +317,6 @@ pub fn alloca_dynamic_arg(f: fn(*mut ()), n: usize) { // missing-NOT: __stack_chk_fail } -// The question then is: in what ways can Rust code generate array-`alloca` -// LLVM instructions? This appears to only be generated by -// rustc_codegen_ssa::traits::Builder::array_alloca() through -// rustc_codegen_ssa::mir::operand::OperandValue::store_unsized(). FWICT -// this is support for the "unsized locals" unstable feature: -// https://doc.rust-lang.org/unstable-book/language-features/unsized-locals.html. - -// CHECK-LABEL: unsized_fn_param{{:|\[}} -#[no_mangle] -pub fn unsized_fn_param(s: [u8], l: bool, f: fn([u8])) { - let n = if l { 1 } else { 2 }; - f(*Box::<[u8]>::from(&s[0..n])); // slice-copy with Box::from - - // Even though slices are conceptually passed by-value both into this - // function and into `f()`, this is implemented with pass-by-reference - // using a suitably constructed fat-pointer (as if the functions - // accepted &[u8]). This function therefore doesn't need dynamic array - // alloca, and is therefore not protected by the `strong` or `basic` - // heuristics. - - // all: __stack_chk_fail - // strong-NOT: __stack_chk_fail - // basic-NOT: __stack_chk_fail - // none-NOT: __stack_chk_fail - // missing-NOT: __stack_chk_fail -} +// rustc can currently (as of 1.98) not generate variable-sized allocas, except for +// variable-sized scalable vector types, so their interaction with stack-protector +// does not need to be tested. From d744bc3feb7c201d7f192fc63b358ba887913a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 10:49:53 +0200 Subject: [PATCH 12/29] remove the location map Rc we can pass it by reference when we have it (loan traversal), and use the one in regioncx's LivenessValues when we don't have it already in scope (MIR dump traversal) --- compiler/rustc_borrowck/src/nll.rs | 2 +- .../src/polonius/constraints.rs | 17 +++++++--------- compiler/rustc_borrowck/src/polonius/dump.rs | 20 ++++++++++--------- compiler/rustc_borrowck/src/polonius/mod.rs | 11 ++++------ .../src/region_infer/region_context.rs | 2 +- .../rustc_borrowck/src/region_infer/values.rs | 15 +++++--------- 6 files changed, 29 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 31fa9dd2ee64e..956f3125de970 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -156,7 +156,7 @@ pub(crate) fn compute_regions<'tcx>( &universal_region_relations.universal_regions, body, move_data, - Rc::clone(&location_map), + &location_map, borrow_set, ); } diff --git a/compiler/rustc_borrowck/src/polonius/constraints.rs b/compiler/rustc_borrowck/src/polonius/constraints.rs index 68e851c05ace3..d445d2e8ef96d 100644 --- a/compiler/rustc_borrowck/src/polonius/constraints.rs +++ b/compiler/rustc_borrowck/src/polonius/constraints.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_index::IndexVec; use rustc_middle::mir::{Body, Location}; @@ -41,8 +39,6 @@ pub(super) struct LocalizedNode { /// The localized constraint graph indexes the physical and logical edges to lazily compute a given /// node's successors during traversal. pub(super) struct LocalizedConstraintGraph { - location_map: Rc, - /// The actual, physical, edges we have recorded for a given node. We localize them on-demand /// when traversing from the node to the successor region. edges: FxHashMap>, @@ -67,7 +63,7 @@ pub(super) trait LocalizedConstraintGraphVisitor { impl LocalizedConstraintGraph { /// Traverses the constraints and returns the indexed graph of edges per node. pub(super) fn new<'tcx>( - location_map: Rc, + location_map: &DenseLocationMap, outlives_constraints: impl Iterator>, ) -> Self { let mut edges: FxHashMap<_, SmallVec<[RegionVid; 4]>> = FxHashMap::default(); @@ -96,7 +92,7 @@ impl LocalizedConstraintGraph { } } - LocalizedConstraintGraph { location_map, edges, logical_edges } + LocalizedConstraintGraph { edges, logical_edges } } /// Traverses the localized constraint graph per-loan, and notifies the `visitor` of discovered @@ -105,6 +101,7 @@ impl LocalizedConstraintGraph { &self, body: &Body<'tcx>, borrow_set: &BorrowSet<'tcx>, + location_map: &DenseLocationMap, liveness_source: &mut impl LivenessSource, visitor: &mut impl LocalizedConstraintGraphVisitor, ) { @@ -119,7 +116,7 @@ impl LocalizedConstraintGraph { let start_node = LocalizedNode { region: loan.region, - point: self.location_map.point_from_location(loan.reserve_location), + point: location_map.point_from_location(loan.reserve_location), }; visited.insert(start_node); stack.push(start_node); @@ -127,7 +124,7 @@ impl LocalizedConstraintGraph { while let Some(node) = stack.pop() { let liveness = liveness_source.liveness_for_region(node.region); // We've reached a node we haven't visited before. - let location = self.location_map.to_location(node.point); + let location = location_map.to_location(node.point); visitor.on_node_traversed(loan_idx, node, liveness.is_live_at(node.point)); // When we find a _new_ successor, we'd like to @@ -183,7 +180,7 @@ impl LocalizedConstraintGraph { for successor_block in body[location.block].terminator().successors() { let next_location = Location { block: successor_block, statement_index: 0 }; - let next_point = self.location_map.point_from_location(next_location); + let next_point = location_map.point_from_location(next_location); if liveness.is_live_at(next_point) { successor_found(LocalizedNode { region: node.region, @@ -226,7 +223,7 @@ impl LocalizedConstraintGraph { statement_index: body[pred_block].statements.len(), }; let previous_point = - self.location_map.point_from_location(previous_location); + location_map.point_from_location(previous_location); successor_found(LocalizedNode { region: node.region, point: previous_point, diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs index fb4c6fbcc9da9..c32b118a9a1cc 100644 --- a/compiler/rustc_borrowck/src/polonius/dump.rs +++ b/compiler/rustc_borrowck/src/polonius/dump.rs @@ -5,7 +5,7 @@ use rustc_index::IndexVec; use rustc_middle::mir::pretty::{MirDumper, PassWhere, PrettyPrintMirOptions}; use rustc_middle::mir::{Body, Location}; use rustc_middle::ty::{RegionVid, TyCtxt}; -use rustc_mir_dataflow::points::PointIndex; +use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; use rustc_session::config::MirIncludeSpans; use crate::borrow_set::BorrowSet; @@ -71,7 +71,8 @@ pub(crate) fn dump_polonius_mir<'tcx>( }; let mut collector = MirDumpCollector::default(); if let Some(graph) = &polonius_context.graph { - graph.traverse(body, borrow_set, &mut liveness_source, &mut collector); + let location_map = regioncx.liveness_constraints().location_map(); + graph.traverse(body, borrow_set, location_map, &mut liveness_source, &mut collector); } let extra_data = &|pass_where, out: &mut dyn io::Write| { @@ -172,7 +173,7 @@ fn emit_polonius_dump<'tcx>( "POLONIUS_CONSTRAINTS" => { edge_count = emit_mermaid_constraint_graph( borrow_set, - regioncx.liveness_constraints(), + regioncx.liveness_constraints().location_map(), &collector.constraints, out, )?; @@ -271,7 +272,7 @@ fn emit_polonius_mir<'tcx>( out, )?; - let liveness = regioncx.liveness_constraints(); + let location_map = regioncx.liveness_constraints().location_map(); // Add localized outlives constraints match pass_where { @@ -281,8 +282,8 @@ fn emit_polonius_mir<'tcx>( for constraint in localized_outlives_constraints { let LocalizedOutlivesConstraint { source, from, target, to } = constraint; - let from = liveness.location_from_point(*from); - let to = liveness.location_from_point(*to); + let from = location_map.to_location(*from); + let to = location_map.to_location(*to); writeln!(out, "| {source:?} at {from:?} -> {target:?} at {to:?}")?; } writeln!(out, "|")?; @@ -462,12 +463,12 @@ fn emit_mermaid_nll_sccs<'tcx>( /// region, and loan introductions. fn emit_mermaid_constraint_graph<'tcx>( borrow_set: &BorrowSet<'tcx>, - liveness: &LivenessValues, + location_map: &DenseLocationMap, localized_outlives_constraints: &[LocalizedOutlivesConstraint], out: &mut dyn io::Write, ) -> io::Result { let node_label = |region: RegionVid, point: PointIndex| { - let location = liveness.location_from_point(point); + let location = location_map.to_location(point); node_name(region, location) }; @@ -533,6 +534,7 @@ fn emit_loan_reachability( reachability: &FxIndexMap>, out: &mut dyn io::Write, ) -> io::Result<()> { + let location_map = liveness.location_map(); for (loan, _) in borrow_set.iter_enumerated() { let Some(reachability) = reachability.get(&loan) else { continue; @@ -553,7 +555,7 @@ fn emit_loan_reachability( for (idx, node) in reachability.iter().enumerate() { writeln!(out, "
  • ")?; - let location = liveness.location_from_point(node.point); + let location = location_map.to_location(node.point); let kind = if idx == 0 { "starts in" } else { "reaches" }; writeln!( out, diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 73133b6ca0811..b19bc6a42db7e 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -39,8 +39,6 @@ pub(crate) mod legacy; mod liveness; mod liveness_constraints; -use std::rc::Rc; - use rustc_data_structures::fx::FxHashSet; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; @@ -143,7 +141,7 @@ impl<'tcx> PoloniusContext<'tcx> { universal_regions: &UniversalRegions<'tcx>, body: &Body<'tcx>, move_data: &MoveData<'tcx>, - location_map: Rc, + location_map: &DenseLocationMap, borrow_set: &BorrowSet<'tcx>, ) { // We don't need to prepare the graph (index NLL constraints, etc.) if we have no loans to @@ -152,8 +150,7 @@ impl<'tcx> PoloniusContext<'tcx> { // From the outlives constraints, liveness, and variances, we can compute reachability // on the lazy localized constraint graph to trace the liveness of loans, for the next // step in the chain (the NLL loan scope and active loans computations). - let graph = - LocalizedConstraintGraph::new(Rc::clone(&location_map), outlives_constraints); + let graph = LocalizedConstraintGraph::new(location_map, outlives_constraints); let local_use_map = self .local_use_map @@ -163,7 +160,7 @@ impl<'tcx> PoloniusContext<'tcx> { std::mem::take(&mut self.deferred_locals_for_liveness); let mut live_loans = LiveLoans::new(location_map.num_points(), borrow_set.len()); let comp = - LivenessComputation::new(infcx, body, &location_map, move_data, &local_use_map); + LivenessComputation::new(infcx, body, location_map, move_data, &local_use_map); let mut liveness_source = DeferredLivenessSource { liveness, live_region_variances: &mut self.live_region_variances, @@ -172,7 +169,7 @@ impl<'tcx> PoloniusContext<'tcx> { comp, }; let mut visitor = LoanLivenessVisitor { live_loans: &mut live_loans }; - graph.traverse(body, borrow_set, &mut liveness_source, &mut visitor); + graph.traverse(body, borrow_set, location_map, &mut liveness_source, &mut visitor); liveness.record_live_loans(live_loans); // The graph can be traversed again during MIR dumping, so we store it here. diff --git a/compiler/rustc_borrowck/src/region_infer/region_context.rs b/compiler/rustc_borrowck/src/region_infer/region_context.rs index af0f48526b4fe..eedfe7d73c644 100644 --- a/compiler/rustc_borrowck/src/region_infer/region_context.rs +++ b/compiler/rustc_borrowck/src/region_infer/region_context.rs @@ -694,7 +694,7 @@ impl<'tcx> RegionInferenceContextInner<'tcx> { /// region is contained within the type of a variable that is live at this point. /// Note: for now, the sets of live loans is only available when using `-Zpolonius=next`. pub(crate) fn is_loan_live_at(&self, loan_idx: BorrowIndex, location: Location) -> bool { - let point = self.liveness_constraints.point_from_location(location); + let point = self.liveness_constraints.location_map().point_from_location(location); self.liveness_constraints.is_loan_live_at(loan_idx, point) } } diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 800f72275b052..709eddf6e985f 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -191,16 +191,6 @@ impl LivenessValues { ) } - #[inline] - pub(crate) fn point_from_location(&self, location: Location) -> PointIndex { - self.location_map.point_from_location(location) - } - - #[inline] - pub(crate) fn location_from_point(&self, point: PointIndex) -> Location { - self.location_map.to_location(point) - } - /// When using `-Zpolonius=next`, records the given live loans for the loan scopes and active /// loans dataflow computations. pub(crate) fn record_live_loans(&mut self, live_loans: LiveLoans) { @@ -214,6 +204,11 @@ impl LivenessValues { .expect("Accessing live loans requires `-Zpolonius=next`") .contains(point, loan_idx) } + + #[inline] + pub(crate) fn location_map(&self) -> &DenseLocationMap { + &self.location_map + } } /// Maps from `ty::PlaceholderRegion` values that are used in the rest of From c9bcd7ad67abb7c751a3972072b2a0e827492e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 10:55:52 +0200 Subject: [PATCH 13/29] more comment --- compiler/rustc_borrowck/src/polonius/liveness.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_borrowck/src/polonius/liveness.rs b/compiler/rustc_borrowck/src/polonius/liveness.rs index 9cbaa2d6ddc4d..df06cc2af7f5a 100644 --- a/compiler/rustc_borrowck/src/polonius/liveness.rs +++ b/compiler/rustc_borrowck/src/polonius/liveness.rs @@ -57,7 +57,9 @@ impl<'a> RegionLiveness<'a> { } /// The data needed to compute region liveness on-demand while traversing the localized outlives -/// constraint graph to compute loan liveness. +/// constraint graph to compute loan liveness. Each region comes from a local's type, so this holds +/// mostly the locals and drop-liveness specifics about their type, to be used as inputs to the +/// `LivenessComputation` giving us the region liveness. #[derive(Default)] pub(crate) struct DeferredLocals<'tcx> { /// For each region, the local whose liveness is deferred. From e34a4bc024394fe99fff455b19add57bf5ed5991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 11:57:19 +0200 Subject: [PATCH 14/29] rename `DeferredLocals` to `DeferredRegionLiveness` we're really deferring the region liveness computation to become on-demand, "deferred locals" is slightly inaccurate in that some of the local's type data is also necessary for drop-liveness. we can focus on the output of the computation rather than its inputs. it also allows us to rename the suboptimal set of locals `deferred` colliding with `deferred_locals` --- .../rustc_borrowck/src/polonius/liveness.rs | 4 ++-- compiler/rustc_borrowck/src/polonius/mod.rs | 13 ++++++------- .../src/type_check/liveness/trace.rs | 19 +++++++++---------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_borrowck/src/polonius/liveness.rs b/compiler/rustc_borrowck/src/polonius/liveness.rs index df06cc2af7f5a..c87ba2045adff 100644 --- a/compiler/rustc_borrowck/src/polonius/liveness.rs +++ b/compiler/rustc_borrowck/src/polonius/liveness.rs @@ -61,7 +61,7 @@ impl<'a> RegionLiveness<'a> { /// mostly the locals and drop-liveness specifics about their type, to be used as inputs to the /// `LivenessComputation` giving us the region liveness. #[derive(Default)] -pub(crate) struct DeferredLocals<'tcx> { +pub(crate) struct DeferredRegionLiveness<'tcx> { /// For each region, the local whose liveness is deferred. /// /// Importantly, because of MIR renumbering, this will always be a 1:1 relationship. @@ -71,7 +71,7 @@ pub(crate) struct DeferredLocals<'tcx> { drop_args_by_local: IndexVec>>>, } -impl<'tcx> DeferredLocals<'tcx> { +impl<'tcx> DeferredRegionLiveness<'tcx> { pub(crate) fn defer_local( &mut self, tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index b19bc6a42db7e..49c6c7f5a5067 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -52,7 +52,7 @@ pub(crate) use self::dump::dump_polonius_mir; pub(crate) use self::liveness_constraints::record_live_region_variance; use crate::constraints::OutlivesConstraint; use crate::dataflow::BorrowIndex; -pub(crate) use crate::polonius::liveness::DeferredLocals; +pub(crate) use crate::polonius::liveness::DeferredRegionLiveness; use crate::polonius::liveness::{LivenessSource, RegionLiveness}; use crate::region_infer::values::LivenessValues; use crate::type_check::liveness::{LivenessComputation, LocalUseMap}; @@ -103,7 +103,7 @@ pub(crate) struct PoloniusContext<'tcx> { /// diagnostics, to focus on the locals we consider relevant and match NLL diagnostics. pub(crate) boring_nll_locals: FxHashSet, - pub(crate) deferred_locals_for_liveness: DeferredLocals<'tcx>, + pub(crate) deferred_liveness: DeferredRegionLiveness<'tcx>, pub(crate) local_use_map: Option, } @@ -156,8 +156,7 @@ impl<'tcx> PoloniusContext<'tcx> { .local_use_map .as_ref() .expect("local use map should be computed before loan liveness"); - let deferred_locals_for_liveness = - std::mem::take(&mut self.deferred_locals_for_liveness); + let deferred_liveness = std::mem::take(&mut self.deferred_liveness); let mut live_loans = LiveLoans::new(location_map.num_points(), borrow_set.len()); let comp = LivenessComputation::new(infcx, body, location_map, move_data, &local_use_map); @@ -165,7 +164,7 @@ impl<'tcx> PoloniusContext<'tcx> { liveness, live_region_variances: &mut self.live_region_variances, universal_regions, - deferred_locals_for_liveness, + deferred_liveness, comp, }; let mut visitor = LoanLivenessVisitor { live_loans: &mut live_loans }; @@ -185,14 +184,14 @@ struct DeferredLivenessSource<'a, 'tcx> { liveness: &'a mut LivenessValues, live_region_variances: &'a mut LiveRegionVariances, universal_regions: &'a UniversalRegions<'tcx>, - deferred_locals_for_liveness: DeferredLocals<'tcx>, + deferred_liveness: DeferredRegionLiveness<'tcx>, comp: LivenessComputation<'a, 'tcx>, } impl<'a> LivenessSource for DeferredLivenessSource<'a, '_> { #[inline] fn liveness_for_region(&mut self, region: RegionVid) -> RegionLiveness<'_> { - self.deferred_locals_for_liveness.compute_deferred_local( + self.deferred_liveness.compute_deferred_local( region, self.universal_regions, &mut self.liveness, diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 21b2e0f304b05..37347bc9c9e16 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -18,7 +18,7 @@ use rustc_trait_selection::traits::query::dropck_outlives; use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOpOutput}; use tracing::debug; -use crate::polonius::{DeferredLocals, LiveRegionVariances, record_live_region_variance}; +use crate::polonius::{DeferredRegionLiveness, LiveRegionVariances, record_live_region_variance}; use crate::region_infer::values::LivenessValues; use crate::type_check::liveness::local_use_map::LocalUseMap; use crate::type_check::liveness::make_all_regions_live; @@ -64,16 +64,15 @@ pub(super) fn trace<'tcx>( let mut results = LivenessResults::new(typeck, comp); - let mut deferred_locals = DeferredLocals::default(); - results.record_legacy_polonius_drop_facts(relevant_live_locals, &deferred); results.compute_for_all_locals(relevant_live_locals); - results.dropck_boring_locals(boring_locals, &deferred, &mut deferred_locals); + let mut deferred_liveness = DeferredRegionLiveness::default(); + results.dropck_boring_locals(boring_locals, &deferred, &mut deferred_liveness); if let Some(polonius_context) = &mut typeck.polonius_context { - polonius_context.deferred_locals_for_liveness = deferred_locals; + polonius_context.deferred_liveness = deferred_liveness; polonius_context.local_use_map = Some(local_use_map); } } @@ -198,10 +197,10 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { &mut self, boring_locals: &[Local], deferred: &FxIndexSet, - deferred_locals: &mut DeferredLocals<'tcx>, + deferred_liveness: &mut DeferredRegionLiveness<'tcx>, ) { for &local in boring_locals { - self.dropck_boring_local(local, deferred, deferred_locals); + self.dropck_boring_local(local, deferred, deferred_liveness); } } @@ -209,7 +208,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { &mut self, local: Local, deferred: &FxIndexSet, - deferred_locals: &mut DeferredLocals<'tcx>, + deferred_liveness: &mut DeferredRegionLiveness<'tcx>, ) { let typeck = &mut *self.typeck; let local_ty = self.comp.body.local_decls[local].ty; @@ -220,7 +219,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // Calculating drop-liveness is expensive, but we can skip it when we know that there // are *no* drops (which is relatively cheap). if deferred.contains(&local) && self.comp.local_use_map.drops(local).next().is_none() { - deferred_locals.defer_local( + deferred_liveness.defer_local( typeck.infcx.tcx, typeck.universal_regions, local, @@ -264,7 +263,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { } // Finally, we mark that this local is deferred, including the drop kinds. - deferred_locals.defer_local( + deferred_liveness.defer_local( typeck.infcx.tcx, typeck.universal_regions, local, From c0d719b96ffe7035519980a7e46e147949645e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 12:04:23 +0200 Subject: [PATCH 15/29] use the now available name `deferred_locals` rename the various `deferred` to mention locals, now that `DeferredLocals` doesn't use `deferred_locals` --- .../src/type_check/liveness/mod.rs | 12 +++++++----- .../src/type_check/liveness/trace.rs | 18 +++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs index 905547c7dce51..1d081973a42ca 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs @@ -82,11 +82,13 @@ pub(super) fn generate<'tcx>( let (polonius_relevant, _) = compute_relevant_live_locals(typeck.tcx(), &free_regions, typeck.body); - let boring: FxHashSet<_> = boring_locals.iter().copied().collect(); - let deferred = - polonius_relevant.into_iter().filter(|local| boring.contains(local)).collect(); - typeck.polonius_context.as_mut().unwrap().boring_nll_locals = boring; - deferred + let boring_nll_locals: FxHashSet<_> = boring_locals.iter().copied().collect(); + let deferred_polonius_locals = polonius_relevant + .into_iter() + .filter(|local| boring_nll_locals.contains(local)) + .collect(); + typeck.polonius_context.as_mut().unwrap().boring_nll_locals = boring_nll_locals; + deferred_polonius_locals }; trace::trace( diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 37347bc9c9e16..d8baca376b0af 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -46,13 +46,13 @@ pub(super) fn trace<'tcx>( move_data: &MoveData<'tcx>, relevant_live_locals: &[Local], boring_locals: &[Local], - deferred: &FxIndexSet, + deferred_locals: &FxIndexSet, ) { let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness_trace"); // The use map must also cover the deferred locals: their liveness is computed later, from // this same map, when the loan liveness traversal first reaches one of their regions. - let use_map_locals = relevant_live_locals.iter().chain(deferred).copied(); + let use_map_locals = relevant_live_locals.iter().chain(deferred_locals).copied(); let local_use_map = LocalUseMap::build(use_map_locals, location_map, typeck.body); let comp = LivenessComputation::new( typeck.infcx, @@ -64,12 +64,12 @@ pub(super) fn trace<'tcx>( let mut results = LivenessResults::new(typeck, comp); - results.record_legacy_polonius_drop_facts(relevant_live_locals, &deferred); + results.record_legacy_polonius_drop_facts(relevant_live_locals, &deferred_locals); results.compute_for_all_locals(relevant_live_locals); let mut deferred_liveness = DeferredRegionLiveness::default(); - results.dropck_boring_locals(boring_locals, &deferred, &mut deferred_liveness); + results.dropck_boring_locals(boring_locals, &deferred_locals, &mut deferred_liveness); if let Some(polonius_context) = &mut typeck.polonius_context { polonius_context.deferred_liveness = deferred_liveness; @@ -196,18 +196,18 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn dropck_boring_locals( &mut self, boring_locals: &[Local], - deferred: &FxIndexSet, + deferred_locals: &FxIndexSet, deferred_liveness: &mut DeferredRegionLiveness<'tcx>, ) { for &local in boring_locals { - self.dropck_boring_local(local, deferred, deferred_liveness); + self.dropck_boring_local(local, deferred_locals, deferred_liveness); } } fn dropck_boring_local( &mut self, local: Local, - deferred: &FxIndexSet, + deferred_locals: &FxIndexSet, deferred_liveness: &mut DeferredRegionLiveness<'tcx>, ) { let typeck = &mut *self.typeck; @@ -218,7 +218,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // in turn would have skipped calculating dropck *at all* for locals without drop-liveness. // Calculating drop-liveness is expensive, but we can skip it when we know that there // are *no* drops (which is relatively cheap). - if deferred.contains(&local) && self.comp.local_use_map.drops(local).next().is_none() { + if deferred_locals.contains(&local) && self.comp.local_use_map.drops(local).next().is_none() { deferred_liveness.defer_local( typeck.infcx.tcx, typeck.universal_regions, @@ -237,7 +237,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { let drop_data = dropck_local(&typeck.infcx, &mut self.drop_data, local_ty, local_span); // We are done with *truly* boring locals. - if !deferred.contains(&local) { + if !deferred_locals.contains(&local) { return; } From a17bea344901ad8e67ad34dbb2e1227dee17585c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 12:06:56 +0200 Subject: [PATCH 16/29] collapse interesting construct --- compiler/rustc_borrowck/src/type_check/liveness/mod.rs | 8 +++----- compiler/rustc_borrowck/src/type_check/liveness/trace.rs | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs index 1d081973a42ca..b2e8ef5304e4f 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs @@ -69,15 +69,13 @@ pub(super) fn generate<'tcx>( // for now. // FIXME: this NLL optimization idea, to reduce work to relevant locals only, still makes sense // for polonius, and should be investigated to improve liveness performance. - let deferred_locals = 'deferred: { + let deferred_locals = if typeck.polonius_context.is_none() || typeck.borrow_set.len() == 0 { // If we aren't going to be using the additional liveness information, // don't even bother computing the larger relevant set. // Similarly, since this liveness information is ultimately used for *loan* // liveness, we don't need to compute it when there are no loans. - if typeck.polonius_context.is_none() || typeck.borrow_set.len() == 0 { - break 'deferred FxIndexSet::default(); - } - + FxIndexSet::default() + } else { let free_regions = typeck.universal_regions.universal_regions_iter().collect(); let (polonius_relevant, _) = compute_relevant_live_locals(typeck.tcx(), &free_regions, typeck.body); diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index d8baca376b0af..d8a7990f86b9c 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -218,7 +218,8 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // in turn would have skipped calculating dropck *at all* for locals without drop-liveness. // Calculating drop-liveness is expensive, but we can skip it when we know that there // are *no* drops (which is relatively cheap). - if deferred_locals.contains(&local) && self.comp.local_use_map.drops(local).next().is_none() { + if deferred_locals.contains(&local) && self.comp.local_use_map.drops(local).next().is_none() + { deferred_liveness.defer_local( typeck.infcx.tcx, typeck.universal_regions, From 63b65639dad919ee1d0a7b45535e5c88769b1e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 14:36:43 +0200 Subject: [PATCH 17/29] micro-optimize gathering polonius-relevant locals by not calling `compute_relevant_live_locals`: - remove free regions hashset allocation - remove unused boring locals allocation - remove relevant locals vec allocation - directly build relevant locals with nll_boring locals filtering we could also optimize the boring nll locals hashset but it's unclear whether it's worth it compared to when it's being iterated. --- .../src/type_check/liveness/mod.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs index b2e8ef5304e4f..735fd68a2b1d2 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs @@ -76,15 +76,33 @@ pub(super) fn generate<'tcx>( // liveness, we don't need to compute it when there are no loans. FxIndexSet::default() } else { - let free_regions = typeck.universal_regions.universal_regions_iter().collect(); - let (polonius_relevant, _) = - compute_relevant_live_locals(typeck.tcx(), &free_regions, typeck.body); - + // As described above, we can defer computing liveness for the regions that are + // NLL-boring-and-polonius-relevant. Let's find these. + // + // Note that this is basically a simplified version of `compute_relevant_live_locals` + // avoiding the allocations that are unnecessary in our more limited use-case. + let tcx = typeck.tcx(); + let universal_regions = typeck.universal_regions; let boring_nll_locals: FxHashSet<_> = boring_locals.iter().copied().collect(); - let deferred_polonius_locals = polonius_relevant - .into_iter() - .filter(|local| boring_nll_locals.contains(local)) + let deferred_polonius_locals = typeck + .body + .local_decls + .iter_enumerated() + .filter_map(|(local, decl)| { + // The polonius-relevant locals are the ones whose types do not only contain + // universal regions. + if boring_nll_locals.contains(&local) + && !tcx.all_free_regions_meet(&decl.ty, |r| { + universal_regions.is_universal_region(r.as_var()) + }) + { + Some(local) + } else { + None + } + }) .collect(); + typeck.polonius_context.as_mut().unwrap().boring_nll_locals = boring_nll_locals; deferred_polonius_locals }; From bf6b17e48c4e62578b7566cfb6069e7b753fc19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 16:03:11 +0200 Subject: [PATCH 18/29] slight cleanups in `DeferredRegionLiveness` --- compiler/rustc_borrowck/src/polonius/liveness.rs | 7 ++++--- compiler/rustc_borrowck/src/polonius/mod.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_borrowck/src/polonius/liveness.rs b/compiler/rustc_borrowck/src/polonius/liveness.rs index c87ba2045adff..346f3622ebdbb 100644 --- a/compiler/rustc_borrowck/src/polonius/liveness.rs +++ b/compiler/rustc_borrowck/src/polonius/liveness.rs @@ -72,6 +72,8 @@ pub(crate) struct DeferredRegionLiveness<'tcx> { } impl<'tcx> DeferredRegionLiveness<'tcx> { + /// We want to defer computing the liveness of the region in the local's type, so we store the + /// necessary inputs to do that later. pub(crate) fn defer_local( &mut self, tcx: TyCtxt<'tcx>, @@ -88,7 +90,6 @@ impl<'tcx> DeferredRegionLiveness<'tcx> { // Then, we want to map all the regions contained within this local to // the local itself. Later, when asked for liveness of a given region, // we can trace liveness for the local containing it. - let by_region = &mut self.by_region; tcx.for_each_free_region(&local_ty, |region| { // See note in `VarianceExtractor::record_variance`. if region.is_bound() || region.is_erased() { @@ -97,7 +98,7 @@ impl<'tcx> DeferredRegionLiveness<'tcx> { let vid = universal_regions.to_region_vid(region); // Because of MIR renumbering, we should always have a 1:1 mapping // between a region and a local. - let previous = by_region.insert(vid, local); + let previous = self.by_region.insert(vid, local); debug_assert!( previous.is_none(), "{vid:?} is in the type of both {previous:?} and {local:?}, but \ @@ -108,7 +109,7 @@ impl<'tcx> DeferredRegionLiveness<'tcx> { /// For a given region, compute the liveness for the local containing it, if it is deferred. #[inline] - pub(crate) fn compute_deferred_local( + pub(crate) fn ensure_deferred_liveness( &mut self, region: RegionVid, universal_regions: &UniversalRegions<'tcx>, diff --git a/compiler/rustc_borrowck/src/polonius/mod.rs b/compiler/rustc_borrowck/src/polonius/mod.rs index 49c6c7f5a5067..664bcedfc5ef2 100644 --- a/compiler/rustc_borrowck/src/polonius/mod.rs +++ b/compiler/rustc_borrowck/src/polonius/mod.rs @@ -191,7 +191,7 @@ struct DeferredLivenessSource<'a, 'tcx> { impl<'a> LivenessSource for DeferredLivenessSource<'a, '_> { #[inline] fn liveness_for_region(&mut self, region: RegionVid) -> RegionLiveness<'_> { - self.deferred_liveness.compute_deferred_local( + self.deferred_liveness.ensure_deferred_liveness( region, self.universal_regions, &mut self.liveness, From b835ee0686a26772ce8896ac729db5fc3e27914f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Thu, 24 Sep 2026 16:05:39 +0200 Subject: [PATCH 19/29] cleanup in `dropck_boring_local` --- .../rustc_borrowck/src/type_check/liveness/trace.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index d8a7990f86b9c..05bd139934e4d 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -200,14 +200,15 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { deferred_liveness: &mut DeferredRegionLiveness<'tcx>, ) { for &local in boring_locals { - self.dropck_boring_local(local, deferred_locals, deferred_liveness); + let is_local_deferred = deferred_locals.contains(&local); + self.dropck_boring_local(local, is_local_deferred, deferred_liveness); } } fn dropck_boring_local( &mut self, local: Local, - deferred_locals: &FxIndexSet, + is_local_deferred: bool, deferred_liveness: &mut DeferredRegionLiveness<'tcx>, ) { let typeck = &mut *self.typeck; @@ -218,8 +219,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // in turn would have skipped calculating dropck *at all* for locals without drop-liveness. // Calculating drop-liveness is expensive, but we can skip it when we know that there // are *no* drops (which is relatively cheap). - if deferred_locals.contains(&local) && self.comp.local_use_map.drops(local).next().is_none() - { + if is_local_deferred && self.comp.local_use_map.drops(local).next().is_none() { deferred_liveness.defer_local( typeck.infcx.tcx, typeck.universal_regions, @@ -238,7 +238,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { let drop_data = dropck_local(&typeck.infcx, &mut self.drop_data, local_ty, local_span); // We are done with *truly* boring locals. - if !deferred_locals.contains(&local) { + if !is_local_deferred { return; } From 21c62776cd1f7ce9a6b06744313d6282f1d0c21a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 21 Sep 2026 22:02:30 +0200 Subject: [PATCH 20/29] Fix invalid jump to def link when `#[rustc_allow_incoherent_impl]` is involved --- src/librustdoc/html/format.rs | 109 ++++++++++++++++-- .../jump-to-def/auxiliary/incoherent_impl1.rs | 18 +++ .../jump-to-def/auxiliary/incoherent_impl2.rs | 5 + .../jump-to-def/incoherent_impl.rs | 24 ++++ 4 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl1.rs create mode 100644 tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl2.rs create mode 100644 tests/rustdoc-html/jump-to-def/incoherent_impl.rs diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 4ee91d411903a..da88172c688f5 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -426,6 +426,81 @@ fn impl_self_ty(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Ty<'_> { .unwrap_or(ty.skip_binder()) } +fn transitive_reexport_path(tcx: TyCtxt<'_>, def_id: DefId) -> Option> { + transitive_reexport_path_inner(tcx, def_id, &mut Vec::new()) +} + +/// Simplified implementation of `rustc_middle::ty::print::pretty::try_print_visible_def_path_recur`. +fn transitive_reexport_path_inner( + tcx: TyCtxt<'_>, + def_id: DefId, + callers: &mut Vec, +) -> Option> { + use rustc_hir::def_id::ModId; + use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; + + if let Some(cnum) = def_id.as_crate_root() { + return Some(vec![tcx.crate_name(cnum)]); + } + + let visible_parent_map = tcx.visible_parent_map(()); + let mut cur_def_key = tcx.def_key(def_id); + + // For a constructor, we want the name of its parent rather than . + if let DefPathData::Ctor = cur_def_key.disambiguated_data.data { + let parent = DefId { + krate: def_id.krate, + index: cur_def_key + .parent + .expect("`DefPathData::Ctor` / `VariantData` missing a parent"), + }; + + cur_def_key = tcx.def_key(parent); + } + + let visible_parent = visible_parent_map.get(&def_id).cloned()?; + // FIXME: Should we also check for private items? + if tcx.is_doc_hidden(visible_parent) { + return None; + } + + let actual_parent = tcx.opt_parent(def_id); + let mut data = cur_def_key.disambiguated_data.data; + match data { + DefPathData::TypeNs(ref mut name) if Some(visible_parent) != actual_parent => { + // Item might be re-exported several times, but filter for the one + // that's public and whose identifier isn't `_`. + let reexport = tcx + .module_children(ModId::new_unchecked(visible_parent)) + .iter() + .filter(|child| child.res.opt_def_id() == Some(def_id)) + .find(|child| child.vis.is_public() && child.ident.name != kw::Underscore) + .map(|child| child.ident.name); + + if let Some(new_name) = reexport { + *name = new_name; + } else { + // There is no name that is public and isn't `_`, so bail. + return None; + } + } + // Re-exported `extern crate`. + DefPathData::CrateRoot => { + data = DefPathData::TypeNs(tcx.crate_name(def_id.krate)); + } + _ => {} + } + + if callers.contains(&visible_parent) { + return None; + } + callers.push(visible_parent); + let mut path = transitive_reexport_path_inner(tcx, visible_parent, callers)?; + callers.pop(); + path.push(DisambiguatedDefPathData { data, disambiguator: 0 }.as_sym(false)); + Some(path) +} + fn generate_item_def_id_path( mut def_id: DefId, original_def_id: DefId, @@ -435,10 +510,13 @@ fn generate_item_def_id_path( let tcx = cx.tcx(); let crate_name = tcx.crate_name(def_id.krate); let mut prim = None; + let mut maybe_have_impl_not_in_def_crate = false; // No need to try to infer the actual parent item if it's not an associated item from the `impl` // block. - if def_id != original_def_id && matches!(tcx.def_kind(def_id), DefKind::Impl { .. }) { + if def_id != original_def_id + && let DefKind::Impl { of_trait } = tcx.def_kind(def_id) + { let ty = impl_self_ty(tcx, def_id); // If this is a dyn trait, we want to get the actual trait from which the method comes from. // Since a `dyn trait` (as of 2026) can only be composed of a trait plus auto traits, we @@ -454,18 +532,35 @@ fn generate_item_def_id_path( def_id = trait_def_id; } else if let Some(new_def_id) = ty.ty_adt_def().map(|adt| adt.did()) { def_id = new_def_id; + maybe_have_impl_not_in_def_crate = !of_trait + && !original_def_id.is_local() + && !def_id.is_local() + && def_id.krate != original_def_id.krate; } else { prim = PrimitiveType::from_ty(ty); } } - let mut fqp = vec![crate_name]; - let shortty = if let Some(prim) = prim { - fqp.push(prim.as_sym()); - ItemType::Primitive + let (shortty, fqp) = if let Some(prim) = prim { + (ItemType::Primitive, vec![crate_name, prim.as_sym()]) } else { - fqp.append(&mut clean::inline::item_relative_path(tcx, def_id)); - ItemType::from_def_id(def_id, tcx) + ( + ItemType::from_def_id(def_id, tcx), + if maybe_have_impl_not_in_def_crate + // We have a method, not coming from a trait, implemented from a different crate + // where the original item is defined. So in short, the item is using + // `#[rustc_allow_incoherent_impl]` and we need to keep the non-final item path. + // Sadly if we use `item_relative_path` which uses `def_path`, it renders the final + // item path and not the intermediate one. + && let Some(fqp) = transitive_reexport_path(tcx, def_id) + { + fqp + } else { + let mut fqp = vec![crate_name]; + fqp.append(&mut clean::inline::item_relative_path(tcx, def_id)); + fqp + }, + ) }; let module_fqp = to_module_fqp(shortty, &fqp); diff --git a/tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl1.rs b/tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl1.rs new file mode 100644 index 0000000000000..a67fe5ab7eee3 --- /dev/null +++ b/tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl1.rs @@ -0,0 +1,18 @@ +//@ aux-build: incoherent_impl2.rs +//@ build-aux-docs + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +extern crate incoherent_impl2 as baz; + +pub mod error { + pub use baz::Error; +} + +impl baz::Error { + #[rustc_allow_incoherent_impl] + pub fn new() -> Self { + Self + } +} diff --git a/tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl2.rs b/tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl2.rs new file mode 100644 index 0000000000000..d04c24de248eb --- /dev/null +++ b/tests/rustdoc-html/jump-to-def/auxiliary/incoherent_impl2.rs @@ -0,0 +1,5 @@ +#![feature(rustc_attrs)] +#![allow(internal_features)] + +#[rustc_has_incoherent_inherent_impls] +pub struct Error; diff --git a/tests/rustdoc-html/jump-to-def/incoherent_impl.rs b/tests/rustdoc-html/jump-to-def/incoherent_impl.rs new file mode 100644 index 0000000000000..d1aeea39e7aa9 --- /dev/null +++ b/tests/rustdoc-html/jump-to-def/incoherent_impl.rs @@ -0,0 +1,24 @@ +// This test ensures that we generate correct links when we target a method implemented +// with `#[rustc_allow_incoherent_impl]` in a different crate than where the type is defined. +// Regression test for . + +//@ aux-build: incoherent_impl1.rs +//@ build-aux-docs +//@ compile-flags: -Zunstable-options --generate-link-to-definition + +#![crate_name = "foo"] + +extern crate incoherent_impl1 as bar; + +//@ has 'src/foo/incoherent_impl.rs.html' +//@ has - '//pre//a[@href="../../incoherent_impl1/error/struct.Error.html#method.new"]' 'new' +//@ has - '//pre//a[@href="../../incoherent_impl2/struct.Error.html"]' 'Error' + +// Now we check that the target files exist and contain the information we want. +//@ has 'incoherent_impl1/error/struct.Error.html' +//@ has - '//*[@id="method.new"]' 'pub fn new() -> Self' +//@ has 'incoherent_impl2/struct.Error.html' + +fn foo() { + let x = bar::error::Error::new(); +} From 3e574ad92e6fb0461d7559b461b22aaa26aa3848 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Fri, 25 Sep 2026 17:29:07 +0200 Subject: [PATCH 21/29] Add some docs to `Global` --- library/alloc/src/alloc.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/library/alloc/src/alloc.rs b/library/alloc/src/alloc.rs index ccac980706571..a900ed91d720c 100644 --- a/library/alloc/src/alloc.rs +++ b/library/alloc/src/alloc.rs @@ -49,8 +49,27 @@ unsafe extern "Rust" { /// to the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// -/// Note: while this type is unstable, the functionality it provides can be -/// accessed through the [free functions in `alloc`](self#functions). +/// Similar to [`alloc`], [`dealloc`], and the other global allocation functions, +/// the way in which calls are forwarded to the allocator registered with +/// `#[global_allocator]` is unspecified. See their safety docs for more information. +/// +/// This allocator must be treated like an opaque allocator that only guarantees +/// the contract described in the docs of [`Allocator`], as well as the following +/// things: +/// * All instances of `Global` are [*equivalent*]. +/// * Allocations from `Global` are only invalidated by calls to de-/reallocating functions. +/// If no such call is made, then the allocation will live for the rest of the program. +/// * The global allocation functions are equivalent to the methods on `Global`, except +/// that they disallow zero-sized allocations, and implicitly ignore any returned +/// excess size. +/// +/// Note that the current implementation of `Global` does not take advantage of +/// some features of [`Allocator`], such as zero-sized allocations (which currently always +/// return a dangling pointer) and overallocating. +/// +/// This may change in the future. You must not rely on it for correctness! +/// +/// [*equivalent*]: Allocator#equivalent-allocators #[stable(feature = "allocator_api", since = "CURRENT_RUSTC_VERSION")] #[derive(Copy, Debug)] #[derive_const(Clone, Default)] From d29d9059f933a1a0c604699457654573aaa7da83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 25 Sep 2026 19:44:08 +0200 Subject: [PATCH 22/29] use `inner` for the `RegionInferenceContextInner` fields --- .../src/region_infer/region_context.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/region_context.rs b/compiler/rustc_borrowck/src/region_infer/region_context.rs index af0f48526b4fe..3e13ae1b032a5 100644 --- a/compiler/rustc_borrowck/src/region_infer/region_context.rs +++ b/compiler/rustc_borrowck/src/region_infer/region_context.rs @@ -83,21 +83,21 @@ pub struct RegionInferenceContextInner<'tcx> { /// This contains data around region constraints and liveness, up to and after solving. /// All data is immutable. pub struct RegionInferenceContext<'tcx> { - data: Frozen>, + inner: Frozen>, } impl<'tcx> Deref for RegionInferenceContext<'tcx> { type Target = RegionInferenceContextInner<'tcx>; fn deref(&self) -> &Self::Target { - &self.data + &self.inner } } /// This contains data around region constraints and liveness, up to solving. /// Calling `solve` returns a new immutable `RegionInferenceContext`. pub(crate) struct UnsolvedRegionInferenceContext<'tcx> { - pub(super) data: RegionInferenceContextInner<'tcx>, + pub(super) inner: RegionInferenceContextInner<'tcx>, /// Type constraints that we check after solving. pub(super) type_tests: Vec>, @@ -107,13 +107,13 @@ impl<'tcx> Deref for UnsolvedRegionInferenceContext<'tcx> { type Target = RegionInferenceContextInner<'tcx>; fn deref(&self) -> &Self::Target { - &self.data + &self.inner } } impl<'tcx> DerefMut for UnsolvedRegionInferenceContext<'tcx> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.data + &mut self.inner } } @@ -774,7 +774,7 @@ impl<'tcx> UnsolvedRegionInferenceContext<'tcx> { } Self { - data: RegionInferenceContextInner { + inner: RegionInferenceContextInner { definitions, liveness_constraints, constraints: outlives_constraints, @@ -838,11 +838,11 @@ impl<'tcx> UnsolvedRegionInferenceContext<'tcx> { let propagated_outlives_requirements = propagated_outlives_requirements.unwrap_or_default(); if propagated_outlives_requirements.is_empty() { - (RegionInferenceContext { data: Frozen::freeze(self.data) }, None, errors_buffer) + (RegionInferenceContext { inner: Frozen::freeze(self.inner) }, None, errors_buffer) } else { let num_external_vids = self.universal_regions().num_global_and_external_regions(); ( - RegionInferenceContext { data: Frozen::freeze(self.data) }, + RegionInferenceContext { inner: Frozen::freeze(self.inner) }, Some(ClosureRegionRequirements { num_external_vids, outlives_requirements: propagated_outlives_requirements, @@ -874,9 +874,9 @@ impl<'tcx> UnsolvedRegionInferenceContext<'tcx> { // dependency order. I.e. a chain A: B: C will visit C, B, A. for scc_a in self.constraint_sccs.all_sccs() { // Walk each SCC `B` such that `A: B`... - for &scc_b in self.data.constraint_sccs.successors(scc_a) { + for &scc_b in self.inner.constraint_sccs.successors(scc_a) { debug!(?scc_b); - self.data.scc_values.add_region(scc_a, scc_b); + self.inner.scc_values.add_region(scc_a, scc_b); } } } From dd39de778bdad176ffc6ee037921fe593aea3030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 25 Sep 2026 19:48:26 +0200 Subject: [PATCH 23/29] reduce field visibility --- compiler/rustc_borrowck/src/region_infer/dump_mir.rs | 2 +- .../src/region_infer/region_context.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index 68f822aac403a..72e16b4b86dfc 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -68,7 +68,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { with_msg: &mut dyn FnMut(&str) -> io::Result<()>, ) -> io::Result<()> { for region in self.definitions.indices() { - let value = self.liveness_constraints.pretty_print_live_points(region); + let value = self.liveness_constraints().pretty_print_live_points(region); if value != "{}" { with_msg(&format!("{region:?} live at {value}"))?; } diff --git a/compiler/rustc_borrowck/src/region_infer/region_context.rs b/compiler/rustc_borrowck/src/region_infer/region_context.rs index 3e13ae1b032a5..fb1f2a1ff106a 100644 --- a/compiler/rustc_borrowck/src/region_infer/region_context.rs +++ b/compiler/rustc_borrowck/src/region_infer/region_context.rs @@ -50,7 +50,7 @@ pub struct RegionInferenceContextInner<'tcx> { /// regions, these start out empty and steadily grow, though for /// each universally quantified region R they start out containing /// the entire CFG and `end(R)`. - pub(super) liveness_constraints: LivenessValues, + liveness_constraints: LivenessValues, /// The outlives constraints computed by the type-check. pub(super) constraints: Frozen>, @@ -58,17 +58,17 @@ pub struct RegionInferenceContextInner<'tcx> { /// The constraint-set, but in graph form, making it easy to traverse /// the constraints adjacent to a particular region. Used to construct /// the SCC (see `constraint_sccs`) and for error reporting. - pub(super) constraint_graph: Frozen, + constraint_graph: Frozen, /// The SCC computed from `constraints` and the constraint /// graph. We have an edge from SCC A to SCC B if `A: B`. Used to /// compute the values of each region. pub(super) constraint_sccs: ConstraintSccs, - pub(super) scc_annotations: IndexVec, + scc_annotations: IndexVec, /// Map universe indexes to information on why we created it. - pub(super) universe_causes: FxIndexMap>, + universe_causes: FxIndexMap>, /// The final inferred values of the region variables; we compute /// one value per SCC. To get the value for any given *region*, @@ -97,10 +97,10 @@ impl<'tcx> Deref for RegionInferenceContext<'tcx> { /// This contains data around region constraints and liveness, up to solving. /// Calling `solve` returns a new immutable `RegionInferenceContext`. pub(crate) struct UnsolvedRegionInferenceContext<'tcx> { - pub(super) inner: RegionInferenceContextInner<'tcx>, + inner: RegionInferenceContextInner<'tcx>, /// Type constraints that we check after solving. - pub(super) type_tests: Vec>, + type_tests: Vec>, } impl<'tcx> Deref for UnsolvedRegionInferenceContext<'tcx> { From 14cd34f24f2937780d2db142e7b054c6df92e546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 25 Sep 2026 20:32:00 +0200 Subject: [PATCH 24/29] move functions that panic on the unsolved regioncx --- .../src/region_infer/region_context.rs | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/region_context.rs b/compiler/rustc_borrowck/src/region_infer/region_context.rs index fb1f2a1ff106a..c12330ce2ad82 100644 --- a/compiler/rustc_borrowck/src/region_infer/region_context.rs +++ b/compiler/rustc_borrowck/src/region_infer/region_context.rs @@ -94,6 +94,26 @@ impl<'tcx> Deref for RegionInferenceContext<'tcx> { } } +impl RegionInferenceContext<'_> { + /// Returns `true` if the region `r` contains the point `p`. + pub(crate) fn region_contains_point(&self, r: RegionVid, p: Location) -> bool { + let scc = self.constraint_sccs.scc(r); + self.scc_values.contains_point(scc, p) + } + + /// Returns the lowest statement index in `start..=end` which is not contained by `r`. + pub(crate) fn first_non_contained_inclusive( + &self, + r: RegionVid, + block: BasicBlock, + start: usize, + end: usize, + ) -> Option { + let scc = self.constraint_sccs.scc(r); + self.scc_values.first_non_contained_inclusive(scc, block, start, end) + } +} + /// This contains data around region constraints and liveness, up to solving. /// Calling `solve` returns a new immutable `RegionInferenceContext`. pub(crate) struct UnsolvedRegionInferenceContext<'tcx> { @@ -143,28 +163,6 @@ impl<'tcx> RegionInferenceContextInner<'tcx> { self.universal_regions().annotate(tcx, err) } - /// Returns `true` if the region `r` contains the point `p`. - /// - /// Panics if called before `solve()` executes, - pub(crate) fn region_contains_point(&self, r: RegionVid, p: Location) -> bool { - let scc = self.constraint_sccs.scc(r); - self.scc_values.contains_point(scc, p) - } - - /// Returns the lowest statement index in `start..=end` which is not contained by `r`. - /// - /// Panics if called before `solve()` executes. - pub(crate) fn first_non_contained_inclusive( - &self, - r: RegionVid, - block: BasicBlock, - start: usize, - end: usize, - ) -> Option { - let scc = self.constraint_sccs.scc(r); - self.scc_values.first_non_contained_inclusive(scc, block, start, end) - } - /// Returns access to the value of `r` for debugging purposes. pub(crate) fn region_value_str(&self, r: RegionVid) -> String { let scc = self.constraint_sccs.scc(r); From 005e44466e2d4549c556421e62e411fbca68242a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 25 Sep 2026 21:12:17 +0200 Subject: [PATCH 25/29] make unique entry point to regioncx creation we previously always created an unsolved regioncx to immediately solve it, but can create the solved regioncx in a single step. this is done by merging `UnsolvedRegionInferenceContext`'s `new` and `solve` into one constructor. --- compiler/rustc_borrowck/src/nll.rs | 16 +- .../src/region_infer/region_context.rs | 310 +++++++++--------- 2 files changed, 165 insertions(+), 161 deletions(-) diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 31fa9dd2ee64e..dd751c5b074b6 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -26,7 +26,7 @@ use crate::polonius::PoloniusContext; use crate::polonius::legacy::{ PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput, }; -use crate::region_infer::{RegionInferenceContext, UnsolvedRegionInferenceContext}; +use crate::region_infer::RegionInferenceContext; use crate::type_check::MirTypeckRegionConstraints; use crate::type_check::free_region_relations::UniversalRegionRelations; use crate::universal_regions::UniversalRegions; @@ -96,14 +96,15 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( &universal_region_relations, infcx, ); - let regioncx = UnsolvedRegionInferenceContext::new( + let (_, closure_region_requirements, _nll_errors) = RegionInferenceContext::solve( &infcx, lowered_constraints, universal_region_relations.clone(), location_map, + body, + None, ); - let (_, closure_region_requirements, _nll_errors) = regioncx.solve(infcx, body, None); closure_region_requirements } @@ -182,17 +183,16 @@ pub(crate) fn compute_regions<'tcx>( } }); - let regioncx = UnsolvedRegionInferenceContext::new( + // Solve the region constraints. + let (regioncx, closure_region_requirements, nll_errors) = RegionInferenceContext::solve( infcx, lowered_constraints, universal_region_relations, location_map, + body, + polonius_output.clone(), ); - // Solve the region constraints. - let (regioncx, closure_region_requirements, nll_errors) = - regioncx.solve(infcx, body, polonius_output.clone()); - NllOutput { regioncx, polonius_input: polonius_facts.map(Box::new), diff --git a/compiler/rustc_borrowck/src/region_infer/region_context.rs b/compiler/rustc_borrowck/src/region_infer/region_context.rs index c12330ce2ad82..20b6616073f57 100644 --- a/compiler/rustc_borrowck/src/region_infer/region_context.rs +++ b/compiler/rustc_borrowck/src/region_infer/region_context.rs @@ -94,7 +94,163 @@ impl<'tcx> Deref for RegionInferenceContext<'tcx> { } } -impl RegionInferenceContext<'_> { +impl<'tcx> RegionInferenceContext<'tcx> { + /// Performs region inference and report errors if we see any + /// unsatisfiable constraints. If this is a closure, returns the + /// region requirements to propagate to our creator, if any. + #[instrument( + skip(infcx, lowered_constraints, location_map, body, polonius_output), + level = "debug" + )] + pub(crate) fn solve( + infcx: &BorrowckInferCtxt<'tcx>, + lowered_constraints: LoweredConstraints<'tcx>, + universal_region_relations: Frozen>, + location_map: Rc, + body: &Body<'tcx>, + polonius_output: Option>, + ) -> (RegionInferenceContext<'tcx>, Option>, RegionErrors<'tcx>) + { + // 1. We first prepare the data needed for the `UnsolvedRegionInferenceContext` to do the + // solving work. + let universal_regions = &universal_region_relations.universal_regions; + + let LoweredConstraints { + constraint_sccs, + definitions, + outlives_constraints, + scc_annotations, + type_tests, + liveness_constraints, + universe_causes, + placeholder_indices, + } = lowered_constraints; + + debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); + debug!("outlives constraints: {:#?}", outlives_constraints); + debug!("placeholder_indices: {:#?}", placeholder_indices); + debug!("type tests: {:#?}", type_tests); + + let constraint_graph = Frozen::freeze(outlives_constraints.graph(definitions.len())); + + if cfg!(debug_assertions) { + sccs_info(infcx, &constraint_sccs); + } + + let mut scc_values = + RegionValues::new(location_map, universal_regions.len(), placeholder_indices); + + // Initializes the region variables with their initial live points. + for (region, definition) in definitions.iter_enumerated() { + let scc = constraint_sccs.scc(region); + + // For each universally quantified region (lifetime parameter). The + // first N variables always correspond to the regions appearing in the + // function signature (both named and anonymous) and in where-clauses. + match definition.origin { + // For each free, universally quantified region X: + NllRegionVariableOrigin::FreeRegion => { + // Add `end(X)` into the set for X. + scc_values.add_free_region(scc, region); + } + + NllRegionVariableOrigin::Placeholder(placeholder) => { + scc_values.add_placeholder(scc, placeholder); + } + + NllRegionVariableOrigin::Existential { .. } => { + // For existential, regions, nothing to do. + } + } + + // Initially copy the liveness constraints of any region that + // has them, setting `scc_values[scc(region)] |= liveness_constraints[region]`. + // + // These values will later be propagated during + // [`UnsolvedRegionInferenceContext::propagate_constraints()`]. + // The values include any live-at-all-points constraints added previously in `liveness::generate`. + if let Some(liveness) = liveness_constraints.point_liveness(region) { + scc_values.merge_liveness(scc, liveness) + } + } + + let mut unsolved_regioncx = UnsolvedRegionInferenceContext { + inner: RegionInferenceContextInner { + definitions, + liveness_constraints, + constraints: outlives_constraints, + constraint_graph, + constraint_sccs, + scc_annotations, + universe_causes, + universal_region_relations, + scc_values, + }, + type_tests, + }; + + // 2. And now we can do the actual solving, the region inference. + let mir_def_id = body.source.def_id(); + unsolved_regioncx.propagate_constraints(); + + let mut errors_buffer = RegionErrors::new(infcx.tcx); + + // If this is a nested body, we propagate unsatisfied + // outlives constraints to the parent body instead of + // eagerly erroing. + let mut propagated_outlives_requirements = + infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new); + + unsolved_regioncx.check_type_tests( + infcx, + propagated_outlives_requirements.as_mut(), + &mut errors_buffer, + ); + + debug!(?errors_buffer); + debug!(?propagated_outlives_requirements); + + // In Polonius mode, the errors about missing universal region relations are in the output + // and need to be emitted or propagated. Otherwise, we need to check whether the + // constraints were too strong, and if so, emit or propagate those errors. + if infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled() { + unsolved_regioncx.check_polonius_subset_errors( + propagated_outlives_requirements.as_mut(), + &mut errors_buffer, + polonius_output + .as_ref() + .expect("Polonius output is unavailable despite `-Z polonius`"), + ); + } else { + unsolved_regioncx.check_universal_regions( + propagated_outlives_requirements.as_mut(), + &mut errors_buffer, + ); + } + + debug!(?errors_buffer); + + let propagated_outlives_requirements = propagated_outlives_requirements.unwrap_or_default(); + if propagated_outlives_requirements.is_empty() { + ( + RegionInferenceContext { inner: Frozen::freeze(unsolved_regioncx.inner) }, + None, + errors_buffer, + ) + } else { + let num_external_vids = + unsolved_regioncx.universal_regions().num_global_and_external_regions(); + ( + RegionInferenceContext { inner: Frozen::freeze(unsolved_regioncx.inner) }, + Some(ClosureRegionRequirements { + num_external_vids, + outlives_requirements: propagated_outlives_requirements, + }), + errors_buffer, + ) + } + } + /// Returns `true` if the region `r` contains the point `p`. pub(crate) fn region_contains_point(&self, r: RegionVid, p: Location) -> bool { let scc = self.constraint_sccs.scc(r); @@ -698,158 +854,6 @@ impl<'tcx> RegionInferenceContextInner<'tcx> { } impl<'tcx> UnsolvedRegionInferenceContext<'tcx> { - /// Creates a new region inference context with a total of - /// `num_region_variables` valid inference variables; the first N - /// of those will be constant regions representing the free - /// regions defined in `universal_regions`. - /// - /// The `outlives_constraints` and `type_tests` are an initial set - /// of constraints produced by the MIR type check. - pub(crate) fn new( - infcx: &BorrowckInferCtxt<'tcx>, - lowered_constraints: LoweredConstraints<'tcx>, - universal_region_relations: Frozen>, - location_map: Rc, - ) -> Self { - let universal_regions = &universal_region_relations.universal_regions; - - let LoweredConstraints { - constraint_sccs, - definitions, - outlives_constraints, - scc_annotations, - type_tests, - liveness_constraints, - universe_causes, - placeholder_indices, - } = lowered_constraints; - - debug!("universal_regions: {:#?}", universal_region_relations.universal_regions); - debug!("outlives constraints: {:#?}", outlives_constraints); - debug!("placeholder_indices: {:#?}", placeholder_indices); - debug!("type tests: {:#?}", type_tests); - - let constraint_graph = Frozen::freeze(outlives_constraints.graph(definitions.len())); - - if cfg!(debug_assertions) { - sccs_info(infcx, &constraint_sccs); - } - - let mut scc_values = - RegionValues::new(location_map, universal_regions.len(), placeholder_indices); - - // Initializes the region variables with their initial live points. - for (region, definition) in definitions.iter_enumerated() { - let scc = constraint_sccs.scc(region); - - // For each universally quantified region (lifetime parameter). The - // first N variables always correspond to the regions appearing in the - // function signature (both named and anonymous) and in where-clauses. - match definition.origin { - // For each free, universally quantified region X: - NllRegionVariableOrigin::FreeRegion => { - // Add `end(X)` into the set for X. - scc_values.add_free_region(scc, region); - } - - NllRegionVariableOrigin::Placeholder(placeholder) => { - scc_values.add_placeholder(scc, placeholder); - } - - NllRegionVariableOrigin::Existential { .. } => { - // For existential, regions, nothing to do. - } - } - - // Initially copy the liveness constraints of any region that - // has them, setting `scc_values[scc(region)] |= liveness_constraints[region]`. - // - // These values will later be propagated during [`Self::propagate_constraints()`]. - // The values include any live-at-all-points constraints added previously in `liveness::generate`. - if let Some(liveness) = liveness_constraints.point_liveness(region) { - scc_values.merge_liveness(scc, liveness) - } - } - - Self { - inner: RegionInferenceContextInner { - definitions, - liveness_constraints, - constraints: outlives_constraints, - constraint_graph, - constraint_sccs, - scc_annotations, - universe_causes, - universal_region_relations, - scc_values, - }, - type_tests, - } - } - - /// Performs region inference and report errors if we see any - /// unsatisfiable constraints. If this is a closure, returns the - /// region requirements to propagate to our creator, if any. - #[instrument(skip(self, infcx, body, polonius_output), level = "debug")] - pub(crate) fn solve( - mut self, - infcx: &InferCtxt<'tcx>, - body: &Body<'tcx>, - polonius_output: Option>, - ) -> (RegionInferenceContext<'tcx>, Option>, RegionErrors<'tcx>) - { - let mir_def_id = body.source.def_id(); - self.propagate_constraints(); - - let mut errors_buffer = RegionErrors::new(infcx.tcx); - - // If this is a nested body, we propagate unsatisfied - // outlives constraints to the parent body instead of - // eagerly erroing. - let mut propagated_outlives_requirements = - infcx.tcx.is_typeck_child(mir_def_id).then(Vec::new); - - self.check_type_tests(infcx, propagated_outlives_requirements.as_mut(), &mut errors_buffer); - - debug!(?errors_buffer); - debug!(?propagated_outlives_requirements); - - // In Polonius mode, the errors about missing universal region relations are in the output - // and need to be emitted or propagated. Otherwise, we need to check whether the - // constraints were too strong, and if so, emit or propagate those errors. - if infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled() { - self.check_polonius_subset_errors( - propagated_outlives_requirements.as_mut(), - &mut errors_buffer, - polonius_output - .as_ref() - .expect("Polonius output is unavailable despite `-Z polonius`"), - ); - } else { - self.check_universal_regions( - propagated_outlives_requirements.as_mut(), - &mut errors_buffer, - ); - } - - debug!(?errors_buffer); - - let propagated_outlives_requirements = propagated_outlives_requirements.unwrap_or_default(); - if propagated_outlives_requirements.is_empty() { - (RegionInferenceContext { inner: Frozen::freeze(self.inner) }, None, errors_buffer) - } else { - let num_external_vids = self.universal_regions().num_global_and_external_regions(); - ( - RegionInferenceContext { inner: Frozen::freeze(self.inner) }, - Some(ClosureRegionRequirements { - num_external_vids, - outlives_requirements: propagated_outlives_requirements, - }), - errors_buffer, - ) - } - } - /// Propagate the region constraints: this will grow the values /// for each region variable until all the constraints are /// satisfied. Note that some values may grow **too** large to be From 29c3e371d1e0e08515f1643061bfd7c848b398ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 25 Sep 2026 22:22:20 +0200 Subject: [PATCH 26/29] update comment and visibility --- compiler/rustc_borrowck/src/region_infer/region_context.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/region_context.rs b/compiler/rustc_borrowck/src/region_infer/region_context.rs index 20b6616073f57..f5b49083da835 100644 --- a/compiler/rustc_borrowck/src/region_infer/region_context.rs +++ b/compiler/rustc_borrowck/src/region_infer/region_context.rs @@ -271,8 +271,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } /// This contains data around region constraints and liveness, up to solving. -/// Calling `solve` returns a new immutable `RegionInferenceContext`. -pub(crate) struct UnsolvedRegionInferenceContext<'tcx> { +struct UnsolvedRegionInferenceContext<'tcx> { inner: RegionInferenceContextInner<'tcx>, /// Type constraints that we check after solving. From 49276b0ec28e161e7f2aa86bfb345f8bf0ed9493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 6 Aug 2026 16:59:59 +0000 Subject: [PATCH 27/29] Detect bad number of generics caused by bad derive When a derive macro expands the annotated item's name directly using `quote!`, it keep the item's Span context (instead of having a new context). This means that the generic Span context machinery which provides feedback that an error happened due to a derive doesn't kick in. If a derive macro isn't written to take into account the existence of type parameters, an error for "mismatched number of type parameters" will be emitted. We now detect the case when this happens due to the derive macro, and customize the output to point that out, as well as avoid giving suggestions that will always be wrong. --- .../wrong_number_of_generic_args.rs | 25 +++++++++++--- .../src/hir_ty_lowering/generics.rs | 2 ++ .../derives/auxiliary/derive-no-generics.rs | 34 +++++++++++++++++++ .../derive-macro-unsupported-type-params.rs | 13 +++++++ ...erive-macro-unsupported-type-params.stderr | 28 +++++++++++++++ 5 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 tests/ui/derives/auxiliary/derive-no-generics.rs create mode 100644 tests/ui/derives/derive-macro-unsupported-type-params.rs create mode 100644 tests/ui/derives/derive-macro-unsupported-type-params.stderr diff --git a/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs index 6717d11d0f3de..f2b166e0b2a01 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics/wrong_number_of_generic_args.rs @@ -3,7 +3,7 @@ use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, Diagnostic, MultiSpan, pluralize}; use rustc_hir as hir; use rustc_middle::ty::{self as ty, AssocItem, AssocItems, TyCtxt}; -use rustc_span::def_id::DefId; +use rustc_span::def_id::{DefId, LocalDefId}; use tracing::debug; /// Handles the `wrong number of type / lifetime / ... arguments` family of error messages. @@ -30,6 +30,9 @@ pub(crate) struct WrongNumberOfGenericArgs<'a, 'tcx> { /// DefId of the generic type pub(crate) def_id: DefId, + + /// DefId of the type that contains the generic + pub(crate) cx_def_id: LocalDefId, } // Provides information about the kind of arguments that were provided for @@ -94,6 +97,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { params_offset: usize, gen_args: &'a hir::GenericArgs<'a>, def_id: DefId, + cx_def_id: LocalDefId, ) -> Self { let angle_brackets = if gen_args.span_ext().is_none() { if gen_args.is_empty() { AngleBrackets::Missing } else { AngleBrackets::Implied } @@ -110,6 +114,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { params_offset, gen_args, def_id, + cx_def_id, } } @@ -1163,10 +1168,20 @@ impl<'a> Diagnostic<'a> for WrongNumberOfGenericArgs<'_, '_> { err.code(E0107); err.span(self.path_segment.ident.span); - self.notify(&mut err); - self.suggest(&mut err); - self.show_definition(&mut err); - self.note_synth_provided(&mut err); + let cx_span = self.tcx.def_span(self.cx_def_id); + if cx_span.in_derive_expansion() { + // Very likely this is a botched `derive` which passes the iten name straight + // through, but doesn't support type parameters. + err.span_label( + cx_span.ctxt().outer_expn_data().call_site, + "this derive macro might not support items with generic parameters", + ); + } else { + self.notify(&mut err); + self.suggest(&mut err); + self.show_definition(&mut err); + self.note_synth_provided(&mut err); + } err } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs index 5f14b32dcabb1..9eda20fa0d9c4 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs @@ -503,6 +503,7 @@ pub(crate) fn check_generic_arg_count( has_self as usize, gen_args, def_id, + cx.item_def_id(), )); Err(reported) @@ -617,6 +618,7 @@ pub(crate) fn check_generic_arg_count( params_offset, gen_args, def_id, + cx.item_def_id(), )) .emit_err_unless_delay(all_params_are_binded || has_invalid_bound) }); diff --git a/tests/ui/derives/auxiliary/derive-no-generics.rs b/tests/ui/derives/auxiliary/derive-no-generics.rs new file mode 100644 index 0000000000000..ea9a2e5b1c89a --- /dev/null +++ b/tests/ui/derives/auxiliary/derive-no-generics.rs @@ -0,0 +1,34 @@ +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::{TokenStream, TokenTree, quote}; + +#[proc_macro_derive(A)] +pub fn derive(item: TokenStream) -> TokenStream { + let mut tokens = item.into_iter(); + let _enum = tokens.next(); + let name = tokens.next().unwrap(); + let _ = tokens.next().unwrap(); + let _ = tokens.next().unwrap(); + let _ = tokens.next().unwrap(); + let TokenTree::Group(group) = tokens.next().unwrap() else { panic!() }; + let mut group = group.stream().into_iter(); + let variant = group.next().unwrap(); + let TokenTree::Group(args) = group.next().unwrap() else { panic!() }; + let arg = args.stream().into_iter().next().unwrap(); + let tokens = quote! { + trait X {} + impl X for $name {} + + impl $name { + fn foo(&self) { + if let Self :: $variant(val) = self { + let _: $arg = val; + } + } + } + + }; + tokens +} diff --git a/tests/ui/derives/derive-macro-unsupported-type-params.rs b/tests/ui/derives/derive-macro-unsupported-type-params.rs new file mode 100644 index 0000000000000..d80064b4eb094 --- /dev/null +++ b/tests/ui/derives/derive-macro-unsupported-type-params.rs @@ -0,0 +1,13 @@ +//@ proc-macro: derive-no-generics.rs +#![crate_type = "lib"] + +#[macro_use] +extern crate derive_no_generics; + +#[derive(A)] +enum A { + //~^ ERROR: missing generics for enum `A` + //~| ERROR: missing generics for enum `A` + Variant(T), + //~^ ERROR: cannot find type `T` in this scope +} diff --git a/tests/ui/derives/derive-macro-unsupported-type-params.stderr b/tests/ui/derives/derive-macro-unsupported-type-params.stderr new file mode 100644 index 0000000000000..e57490e79cc9f --- /dev/null +++ b/tests/ui/derives/derive-macro-unsupported-type-params.stderr @@ -0,0 +1,28 @@ +error[E0425]: cannot find type `T` in this scope + --> $DIR/derive-macro-unsupported-type-params.rs:11:13 + | +LL | Variant(T), + | ^ not found in this scope + +error[E0107]: missing generics for enum `A` + --> $DIR/derive-macro-unsupported-type-params.rs:8:6 + | +LL | #[derive(A)] + | - this derive macro might not support items with generic parameters +LL | enum A { + | ^ + +error[E0107]: missing generics for enum `A` + --> $DIR/derive-macro-unsupported-type-params.rs:8:6 + | +LL | #[derive(A)] + | - this derive macro might not support items with generic parameters +LL | enum A { + | ^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0107, E0425. +For more information about an error, try `rustc --explain E0107`. From fcf74ea7379fb7dfb943e0d2d6090f44f7a665b2 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 22 Sep 2026 10:24:58 +1000 Subject: [PATCH 28/29] Merge `ForceWarning`/`Allow`/`Expect` into `Warning` Because they're all just special cases of warnings. This requires introducing a new `Option` field to `Warning` that describes the special case behaviour; the `DiagInner::lint_id` field also gets merged in. Specific nice things about this: - Removes some unreachable match arms for `Allow`/`Expect`. - Removes the hacky upgrading of `Allow`/`Expect` to `Warning` in `emit_future_breakage_report`. - The types now have structure that used to be maintained by comments and assertions. E.g. it's now impossible to not have a `lint_id` for an `expect` lint. (I always found the `DiagInner::lint_id` field confusing; it's clearer now.) - There's a nice comment on `EmissionOverride` summarizing all the different cases. - A little less code overall. --- compiler/rustc_builtin_macros/src/test.rs | 2 +- compiler/rustc_codegen_llvm/src/back/write.rs | 2 +- compiler/rustc_codegen_ssa/src/back/write.rs | 2 +- .../src/annotate_snippet_emitter_writer.rs | 4 +- compiler/rustc_errors/src/diagnostic.rs | 45 ++--- compiler/rustc_errors/src/json.rs | 16 +- compiler/rustc_errors/src/lib.rs | 176 +++++++++--------- .../rustc_expand/src/proc_macro_server.rs | 2 +- compiler/rustc_lint/src/context.rs | 12 +- compiler/rustc_middle/src/lint.rs | 33 ++-- compiler/rustc_passes/src/dead.rs | 8 +- src/tools/miri/src/diagnostics.rs | 2 +- src/tools/rustfmt/src/parse/session.rs | 8 +- 13 files changed, 137 insertions(+), 175 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 42dbc801bfb5a..010462e5551e0 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -411,7 +411,7 @@ fn not_testable_error(cx: &ExtCtxt<'_>, is_bench: bool, attr_sp: Span, item: Opt let level = match item.map(|i| &i.kind) { // These were a warning before #92959 and need to continue being that to avoid breaking // stable user code (#94508). - Some(ast::ItemKind::MacCall(_)) => Level::Warning, + Some(ast::ItemKind::MacCall(_)) => Level::Warning(None), _ => Level::Error, }; let mut err = Diag::new(dcx, level, msg); diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index dd2dbdff6b82c..deaa78fcda003 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -418,7 +418,7 @@ fn report_inline_asm( }; let level = match level { llvm::DiagnosticLevel::Error => Level::Error, - llvm::DiagnosticLevel::Warning => Level::Warning, + llvm::DiagnosticLevel::Warning => Level::Warning(None), llvm::DiagnosticLevel::Note | llvm::DiagnosticLevel::Remark => Level::Note, }; let msg = msg.trim_prefix("error: ").to_string(); diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 147e4c43c7103..093f801915dab 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -2060,7 +2060,7 @@ impl SharedEmitterMain { sess.dcx().abort_if_errors(); } Ok(SharedEmitterMessage::InlineAsmError(inner)) => { - assert_matches!(inner.level, Level::Error | Level::Warning | Level::Note); + assert_matches!(inner.level, Level::Error | Level::Warning(None) | Level::Note); let mut err = Diag::new(sess.dcx(), inner.level, inner.msg); if !inner.span.is_dummy() { err.span(inner.span.span()); diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index d7e38148806f7..15a435357544d 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -124,12 +124,10 @@ fn annotation_level_for_level(level: Level) -> annotate_snippets::level::Level<' annotate_snippets::Level::ERROR.with_name("error: internal compiler error") } Level::Fatal | Level::Error => annotate_snippets::level::ERROR, - Level::ForceWarning | Level::Warning => annotate_snippets::Level::WARNING, + Level::Warning(_) => annotate_snippets::Level::WARNING, Level::Note => annotate_snippets::Level::NOTE, Level::Help => annotate_snippets::Level::HELP, Level::FailureNote => annotate_snippets::Level::NOTE.no_name(), - Level::Allow => panic!("Should not call with Allow"), - Level::Expect => panic!("Should not call with Expect"), } } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 9688f99abfebb..efd5ead56b36f 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -10,7 +10,7 @@ use rustc_ast::attr::version::RustcVersion; use rustc_data_structures::stable_hash::StableHasher; use rustc_error_messages::{DiagArgMap, DiagArgName, IntoDiagArg}; use rustc_hashes::Hash128; -use rustc_lint_defs::{Applicability, LintExpectationId}; +use rustc_lint_defs::Applicability; use rustc_macros::{Decodable, Encodable}; use rustc_span::{Span, Spanned, Symbol}; use tracing::debug; @@ -190,7 +190,6 @@ pub struct DiagInner { pub messages: Vec<(DiagMessage, Style)>, pub code: Option, - pub lint_id: Option, pub span: MultiSpan, pub children: Vec, pub suggestions: Suggestions, @@ -212,7 +211,6 @@ impl DiagInner { pub fn new_with_messages(level: Level, messages: Vec<(DiagMessage, Style)>) -> Self { DiagInner { level, - lint_id: None, messages, code: None, span: MultiSpan::new(), @@ -234,13 +232,7 @@ impl DiagInner { match self.level { Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => true, - Level::ForceWarning - | Level::Warning - | Level::Note - | Level::Help - | Level::FailureNote - | Level::Allow - | Level::Expect => false, + Level::Warning(_) | Level::Note | Level::Help | Level::FailureNote => false, } } @@ -254,16 +246,6 @@ impl DiagInner { self.is_lint.as_ref().and_then(|is| is.rust_version) } - pub(crate) fn is_force_warn(&self) -> bool { - match self.level { - Level::ForceWarning => { - assert!(self.is_lint.is_some()); - true - } - _ => false, - } - } - pub(crate) fn sub( &mut self, level: Sublevel, @@ -307,7 +289,6 @@ impl DiagInner { level, messages, code, - lint_id: _, // ignore span, children, suggestions, @@ -317,8 +298,16 @@ impl DiagInner { emitted_at: _, // ignore } = self; - let hashed_parts = - (level, messages, code, span, children, suggestions, args.as_slice(), is_lint); + let hashed_parts = ( + std::mem::discriminant(level), // ignore the field within `Warning` + messages, + code, + span, + children, + suggestions, + args.as_slice(), + is_lint, + ); let mut hasher = StableHasher::new(); hashed_parts.hash(&mut hasher); @@ -1099,16 +1088,6 @@ impl<'a> Diag<'a> { self } } - with_fn! { with_lint_id, - /// Add an argument. - pub fn lint_id( - &mut self, - id: LintExpectationId, - ) -> &mut Self { - self.lint_id = Some(id); - self - } } - with_fn! { with_primary_message, /// Add a primary message. pub fn primary_message(&mut self, msg: impl Into) -> &mut Self { diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index 1f5a8c2fe94a6..59d79754314c1 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -136,20 +136,8 @@ impl Emitter for JsonEmitter { fn emit_future_breakage_report(&mut self, diags: Vec) { let data: Vec> = diags .into_iter() - .map(|mut diag| { - // Allowed or expected lints don't normally (by definition) emit a lint - // but future incompat lints are special and are emitted anyway. - // - // So to avoid ICEs and confused users we "upgrade" the lint level for - // those `FutureBreakageItem` to warn. - if matches!(diag.level, crate::Level::Allow | crate::Level::Expect) { - diag.level = crate::Level::Warning; - } - FutureBreakageItem { - diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic( - diag, self, - )), - } + .map(|diag| FutureBreakageItem { + diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic(diag, self)), }) .collect(); let report = FutureIncompatReport { future_incompat_report: data }; diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 33db5577baeb5..4cc5d5c230d15 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -350,9 +350,8 @@ struct DiagCtxtInner { future_breakage_diagnostics: Vec, - /// expected diagnostic will have the level `Expect` which additionally - /// carries the [`LintExpectationId`] of the expectation that can be - /// marked as fulfilled. This is a collection of all [`LintExpectationId`]s + /// Any `expect` lint will carry the [`LintExpectationId`] of the expectation that can be + /// marked as fulfilled. This field is a collection of all [`LintExpectationId`]s /// that have been marked as fulfilled this way. /// /// Emitting expectations after having stolen this field can happen. In particular, an @@ -612,7 +611,7 @@ impl<'a> DiagCtxtHandle<'a> { DelayedBug => { return self.dcx.inner.borrow_mut().emit_diagnostic(diag, self.tainted_with_errors); } - ForceWarning | Warning | Note | Help | FailureNote | Allow | Expect => None, + Warning(_) | Note | Help | FailureNote => None, }; // FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic @@ -787,12 +786,12 @@ impl<'a> DiagCtxtHandle<'a> { match (errors.len(), warnings.len()) { (0, 0) => return, (0, _) => { - // Use `ForceWarning` rather than `Warning` to guarantee emission, e.g. with a - // configuration like `--cap-lints allow --force-warn bare_trait_objects`. - inner.emit_diagnostic( - DiagInner::new(ForceWarning, DiagMessage::Str(warnings)), - None, + // Force emission so this message always prints. + let diag = DiagInner::new( + Warning(Some(EmissionOverride::Forced { lint_id: None })), + DiagMessage::Str(warnings), ); + inner.emit_diagnostic(diag, None); } (_, 0) => { inner.emit_diagnostic(DiagInner::new(Error, errors), self.tainted_with_errors); @@ -918,6 +917,17 @@ impl<'a> DiagCtxtHandle<'a> { inner.emitter.emit_unused_externs(lint_level, unused_externs) } + /// We need to make sure that submitted expectation ids are correctly fulfilled, suppressed, + /// and stored between compilation sessions. To avoid doing these steps manually, we create a + /// dummy diagnostic and emit it as usual, which will be suppressed and stored like a normal + /// expected lint diagnostic. + #[track_caller] + pub fn fulfill_expectation(self, expectation: impl Into) { + let emission_override = Some(EmissionOverride::Expected { lint_id: expectation.into() }); + let msg = "this is a dummy diagnostic, to submit and store an expectation"; + Diag::new(self, Warning(emission_override), msg).emit() + } + /// This methods steals all [`LintExpectationId`]s that are stored inside /// [`DiagCtxtInner`] and indicate that the linked expectation has been fulfilled. #[must_use] @@ -1077,7 +1087,7 @@ impl<'a> DiagCtxtHandle<'a> { #[track_caller] pub fn struct_warn(self, msg: impl Into) -> Diag<'a> { - Diag::new(self, Warning, msg) + Diag::new(self, Warning(None), msg) } #[track_caller] @@ -1101,7 +1111,7 @@ impl<'a> DiagCtxtHandle<'a> { #[track_caller] pub fn create_warn(self, warning: impl Diagnostic<'a>) -> Diag<'a> { - warning.into_diag(self, Warning) + warning.into_diag(self, Warning(None)) } #[track_caller] @@ -1142,16 +1152,6 @@ impl<'a> DiagCtxtHandle<'a> { pub fn emit_note(self, note: impl Diagnostic<'a>) { self.create_note(note).emit() } - - #[track_caller] - pub fn struct_allow(self, msg: impl Into) -> Diag<'a> { - Diag::new(self, Allow, msg) - } - - #[track_caller] - pub fn struct_expect(self, msg: impl Into, id: LintExpectationId) -> Diag<'a> { - Diag::new(self, Expect, msg).with_lint_id(id) - } } impl DiagCtxtInner { @@ -1185,13 +1185,13 @@ impl DiagCtxtInner { let has_errors = !self.err_guars.is_empty(); for (_, stashed_diagnostics) in mem::take(&mut self.stashed_diagnostics).into_iter() { for (_, (diag, _guar, _thread)) in stashed_diagnostics { - if !diag.is_error() { - // Unless they're forced, don't flush stashed warnings when - // there are errors, to avoid causing warning overload. The - // stash would've been stolen already if it were important. - if !diag.is_force_warn() && has_errors { - continue; - } + // When there are errors, skip flushing of stashed unforced warnings, to avoid + // warning overload. (They would have been stolen already if they were important.) + if has_errors + && let Warning(emission_override) = diag.level + && !matches!(emission_override, Some(EmissionOverride::Forced { .. })) + { + continue; } guar = guar.or(self.emit_diagnostic(diag, None)); } @@ -1206,10 +1206,10 @@ impl DiagCtxtInner { taint: Option<&Cell>>, ) -> Option { if diagnostic.has_future_breakage() { - // Future breakages aren't emitted if they're `Level::Allow` or - // `Level::Expect`, but they still need to be constructed and - // stashed below, so they'll trigger the must_produce_diag check. - assert_matches!(diagnostic.level, Error | ForceWarning | Warning | Allow | Expect); + // About the `allow`/`expect` lint sub-cases of `Warning`: future breakages aren't + // emitted for them, but they still need to be handled below so they'll trigger the + // `must_produce_diag` check. + assert_matches!(diagnostic.level, Error | Warning(_)); self.future_breakage_diagnostics.push(diagnostic.clone()); } @@ -1257,38 +1257,37 @@ impl DiagCtxtInner { }; } } - ForceWarning => { - if let Some(lint_id) = diagnostic.lint_id { - self.fulfilled_expectations.insert(lint_id); - } - } - Warning => { - if !self.flags.can_emit_warnings { - // We are not emitting warnings. - if diagnostic.has_future_breakage() { - // The side-effect is at the top of this method. + Warning(emission_override) => { + match emission_override { + None => { + if !self.flags.can_emit_warnings { + // We are not emitting warnings. + if diagnostic.has_future_breakage() { + // The side-effect is at the top of this method. + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + } + return None; + } + } + Some(EmissionOverride::Forced { lint_id: None }) => {} + Some(EmissionOverride::Forced { lint_id: Some(lint_id) }) => { + self.fulfilled_expectations.insert(lint_id); + } + Some(EmissionOverride::Allowed) => { + assert!(diagnostic.has_future_breakage()); TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + self.suppressed_expected_diag = true; + return None; + } + Some(EmissionOverride::Expected { lint_id }) => { + self.fulfilled_expectations.insert(lint_id); + TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); + self.suppressed_expected_diag = true; + return None; } - return None; } } Note | Help | FailureNote => {} - Allow => { - // Nothing emitted for allowed lints. - if diagnostic.has_future_breakage() { - // The side-effect is at the top of this method. - TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); - self.suppressed_expected_diag = true; - } - return None; - } - Expect => { - self.fulfilled_expectations.insert(diagnostic.lint_id.unwrap()); - // Nothing emitted here for expected lints. - TRACK_DIAGNOSTIC(diagnostic, &mut |_| None); - self.suppressed_expected_diag = true; - return None; - } } if let (Some(msrv), Some(diag_msrv)) = (self.msrv, diagnostic.rust_version()) @@ -1344,7 +1343,7 @@ impl DiagCtxtInner { if is_error { self.deduplicated_err_count += 1; - } else if matches!(diagnostic.level, ForceWarning | Warning) { + } else if matches!(diagnostic.level, Warning(_)) { self.deduplicated_warn_count += 1; } self.has_printed = true; @@ -1545,19 +1544,45 @@ impl DelayedDiagInner { } } +/// Special emission behaviours on warning diagnostics. Combines with `DiagInner::is_lint` in the +/// following ways. +/// +/// | case | is_lint | `Option` field in `Warning` +/// | ---- | ------- | --------------------------------------------- +/// | ordinary non-lint diagnostic | None | None +/// | warning-count summary | None | Some(Forced { lint_id: None }) +/// | N/A | None | Some(Forced { lint_id: Some(lint_id) }) +/// | dummy expectation fulfillment | None | Some(Expected { lint_id }) +/// | N/A | None | Some(Allowed) +/// | ordinary `warn` lint | Some | None +/// | `warn` lint at `force-warn` | Some | Some(Forced { lint_id: None }) +/// | `expect` lint at `force-warn` | Some | Some(Forced { lint_id: Some(lint_id) }) +/// | `expect` lint | Some | Some(Expected { lint_id }) +/// | `allow` lint with future breakage | Some | Some(Allowed) +/// +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] +pub enum EmissionOverride { + /// Cases where emission is forced. + Forced { lint_id: Option }, + + /// Used for `allow` lints with future breakage. (Allow lints without future breakage are + /// ignored and therefore never need an `EmissionOverride` value.) + Allowed, + + /// Used for `expect` lints. + Expected { lint_id: LintExpectationId }, +} + /// | Level | is_error | usable emit fns | Top-level | Used in lints? /// | ----- | -------- | --------------- | --------- | -------------- /// | Bug | yes | emit, emit_bug | yes | - /// | Fatal | yes | emit, emit_fatal | yes | - /// | Error | yes | emit, emit_err | yes | yes /// | DelayedBug | yes | emit, emit_err | yes | - -/// | ForceWarning | - | emit | yes | lint-only /// | Warning | - | emit | yes | yes /// | Note | - | emit | rare | - /// | Help | - | emit | don't use | - /// | FailureNote | - | emit | rare | - -/// | Allow | - | emit | yes | lint-only -/// | Expect | - | emit | yes | lint-only /// #[derive(Copy, PartialEq, Eq, Clone, Hash, Debug, Encodable, Decodable)] pub enum Level { @@ -1578,16 +1603,9 @@ pub enum Level { /// that should only be reached when compiling erroneous code. DelayedBug, - /// A `force-warn` lint warning about the code being compiled. Does not prevent compilation - /// from finishing. - /// - /// Requires the corresponding `DiagInner::lint_id` to be `Some` for expected lint diagnostics, - /// and `None` in all other cases. - ForceWarning, - /// A warning about the code being compiled. Does not prevent compilation from finishing. - /// Will be skipped if `can_emit_warnings` is false. - Warning, + /// Might not be emitted, depending on the value of `EmissionOverride` and `can_emit_warnings`. + Warning(Option), /// A rarely-used level for output that isn't an error or a warning. Note, @@ -1602,13 +1620,6 @@ pub enum Level { /// Similar to `Note`, but even rarer. Lacks the a trailing blank line that all other /// diagnostics have. Also, when printed for human consumption it doesn't have a `note:` label. FailureNote, - - /// Only used for lints. - Allow, - - /// Only used for lints. Requires the corresponding `DiagInner::lint_id` to be `Some` to - /// silence the lints. - Expect, } impl fmt::Display for Level { @@ -1622,16 +1633,15 @@ impl Level { match self { Bug | DelayedBug => "error: internal compiler error", Fatal | Error => "error", - ForceWarning | Warning => "warning", + Warning(_) => "warning", Note => "note", Help => "help", FailureNote => "failure-note", - Allow | Expect => unreachable!(), } } - pub fn is_failure_note(&self) -> bool { - matches!(*self, FailureNote) + pub fn is_failure_note(self) -> bool { + matches!(self, FailureNote) } } diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 1ecf808f78839..ce6a0d7548be1 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -407,7 +407,7 @@ impl ToInternal for Level { fn to_internal(self) -> rustc_errors::Level { match self { Level::Error => rustc_errors::Level::Error, - Level::Warning => rustc_errors::Level::Warning, + Level::Warning => rustc_errors::Level::Warning(None), Level::Note => rustc_errors::Level::Note, Level::Help => rustc_errors::Level::Help, _ => unreachable!("unknown proc_macro::Level variant: {:?}", self), diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index a7ded8a82b5dc..15c60c920ce37 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -547,17 +547,7 @@ pub trait LintContext { /// retrieved from the current lint pass. Buffered or manually created ids can /// cause ICEs. fn fulfill_expectation(&self, expectation: Self::LintExpectationId) { - // We need to make sure that submitted expectation ids are correctly fulfilled suppressed - // and stored between compilation sessions. To not manually do these steps, we simply create - // a dummy diagnostic and emit it as usual, which will be suppressed and stored like a - // normal expected lint diagnostic. - self.sess() - .dcx() - .struct_expect( - "this is a dummy diagnostic, to submit and store an expectation", - expectation.into(), - ) - .emit(); + self.sess().dcx().fulfill_expectation(expectation); } } diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 5d631acc1b528..3655d4594aa38 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -2,7 +2,7 @@ use std::cmp::min; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sorted_map::SortedMap; -use rustc_errors::{Diag, DiagLocation, Diagnostic, MultiSpan}; +use rustc_errors::{Diag, DiagLocation, Diagnostic, EmissionOverride, MultiSpan}; use rustc_hir::{HirId, ItemLocalId}; use rustc_lint_defs::{ EditionFcw, FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId, @@ -416,7 +416,7 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>( let err_level = match level { Level::Allow => { if has_future_breakage { - rustc_errors::Level::Allow + rustc_errors::Level::Warning(Some(EmissionOverride::Allowed)) } else { return; } @@ -426,13 +426,17 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>( // we can't return early like in the case for `Level::Allow` because we still // need the lint diagnostic to be emitted to `rustc_error::DiagCtxtInner`. // - // We can also not mark the lint expectation as fulfilled here right away, as it - // can still be cancelled in the decorate function. All of this means that we simply - // create a `Diag` and continue as we would for warnings. - rustc_errors::Level::Expect + // We also cannot mark the lint expectation as fulfilled here right away, because + // it can still be cancelled in the decorate function. So we create a `Diag` and + // continue as we would for `Level::Warn`. + rustc_errors::Level::Warning(Some(EmissionOverride::Expected { + lint_id: lint_id.unwrap(), + })) } - Level::ForceWarn => rustc_errors::Level::ForceWarning, - Level::Warn => rustc_errors::Level::Warning, + Level::ForceWarn => { + rustc_errors::Level::Warning(Some(EmissionOverride::Forced { lint_id })) + } + Level::Warn => rustc_errors::Level::Warning(None), Level::Deny | Level::Forbid => rustc_errors::Level::Error, }; @@ -477,12 +481,13 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>( // emitted or we'll get a `must_produce_diag` ICE. // // When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors: - // 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning` - // or `Error`, then the diagnostic will be emitted regardless of CLI options. + // 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `Error`, then the + // diagnostic will be emitted regardless of CLI options. // 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by // `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic // will be emitted if `can_emit_warnings` is true. - let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings(); + let skip = + err_level == rustc_errors::Level::Warning(None) && !sess.dcx().can_emit_warnings(); let mut err: Diag<'_> = if !skip { decorate(sess.dcx(), err_level) @@ -495,7 +500,8 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>( if let Some(span) = span && err.span.primary_span().is_none() { - // We can't use `err.span()` because it overwrites the labels, so we need to do it manually. + // We can't use `err.span()` because it overwrites the labels, so we need to do it + // manually. for primary in span.primary_spans() { err.span.push_primary_span(*primary); } @@ -503,9 +509,6 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a> + 'a>( err.span.push_span_diag(*label_span, label.clone()); } } - if let Some(lint_id) = lint_id { - err.lint_id(lint_id); - } if disable_suggestions { // Any suggestions made here are likely to be incorrect, so anything we diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index bd1bdf7cc0646..ffa3a7443ecc4 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -1172,13 +1172,7 @@ impl<'tcx> DeadVisitor<'tcx> { && let (_, Some(expectation)) = self.def_lint_level_plus(node) { // Same mechanism as LintContext::fulfill_expectation. - self.tcx - .dcx() - .struct_expect( - "this is a dummy diagnostic, to submit and store an expectation", - expectation.into(), - ) - .emit(); + self.tcx.dcx().fulfill_expectation(expectation); } }; diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index 4d25aed007416..793c7d6f0401b 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -569,7 +569,7 @@ fn report_msg<'tcx>( let tcx = machine.tcx; let level = match diag_level { DiagLevel::Error => Level::Error, - DiagLevel::Warning => Level::Warning, + DiagLevel::Warning => Level::Warning(None), DiagLevel::Note => Level::Note, }; let mut err = Diag::new(tcx.sess.dcx(), level, title); diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs index 83a77e73cb7ea..c61b43492ce9e 100644 --- a/src/tools/rustfmt/src/parse/session.rs +++ b/src/tools/rustfmt/src/parse/session.rs @@ -418,7 +418,7 @@ mod tests { Some(ignore_list), ); let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); - let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span)); + let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(span)); emitter.emit_diagnostic(non_fatal_diagnostic); assert_eq!(num_emitted_errors.load(Ordering::Acquire), 0); assert_eq!(can_reset_errors.load(Ordering::Acquire), true); @@ -439,7 +439,7 @@ mod tests { None, ); let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); - let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span)); + let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(span)); emitter.emit_diagnostic(non_fatal_diagnostic); assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1); assert_eq!(can_reset_errors.load(Ordering::Acquire), false); @@ -467,8 +467,8 @@ mod tests { ); let bar_span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1))); let foo_span = MultiSpan::from_span(mk_sp(BytePos(21), BytePos(22))); - let bar_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(bar_span)); - let foo_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(foo_span)); + let bar_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(bar_span)); + let foo_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(foo_span)); let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, None); emitter.emit_diagnostic(bar_diagnostic); emitter.emit_diagnostic(foo_diagnostic); From 1d910ae376cf35835481f15cfab1561bc25a5e1b Mon Sep 17 00:00:00 2001 From: malezjaa Date: Sat, 26 Sep 2026 00:28:04 +0200 Subject: [PATCH 29/29] simplify ndk compiler test --- src/bootstrap/src/utils/cc_detect/tests.rs | 52 +++++----------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/src/bootstrap/src/utils/cc_detect/tests.rs b/src/bootstrap/src/utils/cc_detect/tests.rs index b31850759687a..34c7901d077e1 100644 --- a/src/bootstrap/src/utils/cc_detect/tests.rs +++ b/src/bootstrap/src/utils/cc_detect/tests.rs @@ -7,11 +7,8 @@ use crate::core::session::Session; use crate::utils::tests::TestCtx; #[test] -fn test_ndk_compiler_c() { +fn test_ndk_compiler() { let ndk_path = PathBuf::from("/ndk"); - let target_triple = "arm-unknown-linux-android"; - let expected_triple_translated = "armv7a-unknown-linux-android"; - let expected_compiler = format!("{}21-{}", expected_triple_translated, Language::C.clang()); let host_tag = if cfg!(target_os = "macos") { "darwin-x86_64" } else if cfg!(target_os = "windows") { @@ -19,50 +16,23 @@ fn test_ndk_compiler_c() { } else { "linux-x86_64" }; - let expected_path = ndk_path - .join("toolchains") - .join("llvm") - .join("prebuilt") - .join(host_tag) - .join("bin") - .join(&expected_compiler); - let result = ndk_compiler(Language::C, target_triple, &ndk_path); - assert_eq!(result, expected_path); -} -#[test] -fn test_ndk_compiler_cpp() { - let ndk_path = PathBuf::from("/ndk"); - let target_triple = "arm-unknown-linux-android"; - let expected_triple_translated = "armv7a-unknown-linux-android"; - let expected_compiler = - format!("{}21-{}", expected_triple_translated, Language::CPlusPlus.clang()); - let host_tag = if cfg!(target_os = "macos") { - "darwin-x86_64" - } else if cfg!(target_os = "windows") { - "windows-x86_64" - } else { - "linux-x86_64" - }; - let expected_path = ndk_path - .join("toolchains") - .join("llvm") - .join("prebuilt") - .join(host_tag) - .join("bin") - .join(&expected_compiler); - let result = ndk_compiler(Language::CPlusPlus, target_triple, &ndk_path); - assert_eq!(result, expected_path); + for (language, compiler) in [ + (Language::C, "armv7a-unknown-linux-android21-clang"), + (Language::CPlusPlus, "armv7a-unknown-linux-android21-clang++"), + ] { + let expected = + ndk_path.join("toolchains/llvm/prebuilt").join(host_tag).join("bin").join(compiler); + + assert_eq!(ndk_compiler(language, "arm-unknown-linux-android", &ndk_path), expected); + } } #[test] -fn test_language_gcc() { +fn test_language_compiler_names() { assert_eq!(Language::C.gcc(), "gcc"); assert_eq!(Language::CPlusPlus.gcc(), "g++"); -} -#[test] -fn test_language_clang() { assert_eq!(Language::C.clang(), "clang"); assert_eq!(Language::CPlusPlus.clang(), "clang++"); }