From 0439f4ef09af50b440af245a0f2173442fbcd567 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:32:21 +0100 Subject: [PATCH 01/42] Hint that memchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 7 ++++++- library/coretests/tests/slice.rs | 11 +++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 1e1053583a617..6762015181d85 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -28,7 +28,12 @@ pub const fn memchr(x: u8, text: &[u8]) -> Option { return memchr_naive(x, text); } - memchr_aligned(x, text) + let result = memchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result } #[inline] diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index a4db7304fff90..b05f54d4df0a2 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1781,6 +1781,17 @@ pub mod memchr { assert_eq!(None, memchr(b'a', b"xyz")); } + #[test] + fn each_alignment() { + let mut data = [1u8; 64]; + let needle = 2; + let pos = 40; + data[pos] = needle; + for start in 0..16 { + assert_eq!(Some(pos - start), memchr(needle, &data[start..])); + } + } + #[test] fn matches_one_reversed() { assert_eq!(Some(0), memrchr(b'a', b"a")); diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs new file mode 100644 index 0000000000000..fbdbdcc3fe9f3 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -0,0 +1,13 @@ +// Ensure `memchr` communicates that a returned index is in bounds. +//@ compile-flags: -Copt-level=3 -Zinline-mir=false +//@ only-64bit + +#![crate_type = "lib"] + +// CHECK-LABEL: @find_char +#[no_mangle] +pub fn find_char(haystack: &str, needle: char) -> Option { + // CHECK-NOT: phi { i64, i64 } + // CHECK: ret { i64, i64 } + haystack.find(needle) +} From c1f36d5f4bde0f955e9d0cbdec406d22b5044360 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:29:23 +0100 Subject: [PATCH 02/42] Hint that memrchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 10 ++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 6762015181d85..c83e8b218da08 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -112,8 +112,18 @@ const fn memchr_aligned(x: u8, text: &[u8]) -> Option { } /// Returns the last index matching the byte `x` in `text`. +#[inline] #[must_use] pub fn memrchr(x: u8, text: &[u8]) -> Option { + let result = memrchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memrchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result +} + +fn memrchr_aligned(x: u8, text: &[u8]) -> Option { // Scan for a single byte value by reading two `usize` words at a time. // // Split `text` in three parts: diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index fbdbdcc3fe9f3..f18335075451c 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -3,6 +3,11 @@ //@ only-64bit #![crate_type = "lib"] +#![feature(slice_internals)] + +extern crate core; + +use core::slice::memchr::memrchr; // CHECK-LABEL: @find_char #[no_mangle] @@ -11,3 +16,11 @@ pub fn find_char(haystack: &str, needle: char) -> Option { // CHECK: ret { i64, i64 } haystack.find(needle) } + +// CHECK-LABEL: @rfind_byte +#[no_mangle] +pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { + // CHECK-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memrchr(needle, haystack).map(|index| haystack[index]) +} From ea475ff2bc035827664c4ad43c4c1e3408e4500f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 14 Jul 2026 11:38:55 +0000 Subject: [PATCH 03/42] add test which should pass --- .../mgca/dyn-non-type-assoc-const-binding.rs | 14 ++++++ .../dyn-non-type-assoc-const-binding.stderr | 45 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs create mode 100644 tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr diff --git a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs new file mode 100644 index 0000000000000..b683af7973913 --- /dev/null +++ b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs @@ -0,0 +1,14 @@ +//@ check-pass +//@ compile-flags: -Znext-solver=globally +//@ dont-require-annotations: NOTE + +#![feature(min_generic_const_args, generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + const ASSOC: usize; +} + +fn foo(_: &dyn Trait) {} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr new file mode 100644 index 0000000000000..05a7fd4fa3b4f --- /dev/null +++ b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr @@ -0,0 +1,45 @@ +error: use of trait associated const not defined as `type const` + --> $DIR/dyn-non-type-assoc-const-binding.rs:12:22 + | +LL | fn foo(_: &dyn Trait) {} + | ^^^^^^^^^^ + | + = note: the declaration in the trait must begin with `type const` not just `const` alone + +error: use of trait associated const not defined as `type const` + --> $DIR/dyn-non-type-assoc-const-binding.rs:12:22 + | +LL | fn foo(_: &dyn Trait) {} + | ^^^^^^^^^^ + | + = note: the declaration in the trait must begin with `type const` not just `const` alone + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: use of trait associated const not defined as `type const` + --> $DIR/dyn-non-type-assoc-const-binding.rs:12:22 + | +LL | fn foo(_: &dyn Trait) {} + | ^^^^^^^^^^ + | + = note: the declaration in the trait must begin with `type const` not just `const` alone + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-non-type-assoc-const-binding.rs:12:12 + | +LL | fn foo(_: &dyn Trait) {} + | ^^^^^^^^^^^^^^^^^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-non-type-assoc-const-binding.rs:9:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const ASSOC: usize; + | ^^^^^ ...because it contains associated const `ASSOC` that's not defined as `type const` + = help: consider moving `ASSOC` to another trait + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0038`. From 62e3e3d0c201cdf1430af6bae63aae00d6b5da31 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 18 Jul 2026 09:20:16 +0000 Subject: [PATCH 04/42] Add helper for equality-constrainable assoc items --- .../src/diagnostics/wrong_number_of_generic_args.rs | 2 +- .../rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs | 6 +++--- compiler/rustc_middle/src/ty/assoc.rs | 5 +++++ compiler/rustc_middle/src/ty/sty.rs | 2 +- .../src/cfi/typeid/itanium_cxx_abi/transform.rs | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) 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 c80c63b7c0188..1a103b9db1fc0 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 @@ -489,7 +489,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { items .in_definition_order() .filter(|item| { - (item.is_type() || item.is_type_const()) + item.can_have_equality_constraint(self.tcx) && !item.is_impl_trait_in_trait() && !self .gen_args diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index c52f87f80b9b2..482d92759b886 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -231,9 +231,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ordered_associated_items.extend( tcx.associated_items(pred.trait_ref.def_id) .in_definition_order() - // Only associated types & type consts can possibly be - // constrained in a trait object type via a binding. - .filter(|item| item.is_type() || item.is_type_const()) + // Only associated items that support equality constraints can + // be constrained in a trait object type via a binding. + .filter(|item| item.can_have_equality_constraint(tcx)) // Traits with RPITITs are simply not dyn compatible (for now). .filter(|item| !item.is_impl_trait_in_trait()) .map(|item| (item.def_id, trait_ref)), diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 85c94d0b598e6..4999e6426f434 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -142,6 +142,11 @@ impl AssocItem { matches!(self.kind, ty::AssocKind::Const { is_type_const: true, .. }) } + /// Whether this associated item can be constrained with an equality binding. + pub fn can_have_equality_constraint(&self, _tcx: TyCtxt<'_>) -> bool { + self.is_type() || self.is_type_const() + } + pub fn is_fn(&self) -> bool { matches!(self.kind, ty::AssocKind::Fn { .. }) } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index ff787c0ede7f7..769c90024c76b 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -771,7 +771,7 @@ impl<'tcx> Ty<'tcx> { .map(|principal| { tcx.associated_items(principal.def_id()) .in_definition_order() - .filter(|item| item.is_type() || item.is_type_const()) + .filter(|item| item.can_have_equality_constraint(tcx)) .filter(|item| !item.is_impl_trait_in_trait()) .filter(|item| !tcx.generics_require_sized_self(item.def_id)) .count() diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs index 6b3554331b420..6bc1647c4b05b 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs @@ -241,7 +241,7 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc .flat_map(|super_poly_trait_ref| { tcx.associated_items(super_poly_trait_ref.def_id()) .in_definition_order() - .filter(|item| item.is_type() || item.is_type_const()) + .filter(|item| item.can_have_equality_constraint(tcx)) .filter(|item| !tcx.generics_require_sized_self(item.def_id)) .map(move |assoc_item| { super_poly_trait_ref.map_bound(|super_trait_ref| { From f86e9f6fc681ca7a8c44d458a3e0c10cefb93e4f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 18 Jul 2026 09:28:20 +0000 Subject: [PATCH 05/42] Allow associated const equality constraints with MGCA --- .../wrong_number_of_generic_args.rs | 5 +- .../src/hir_ty_lowering/bounds.rs | 27 ++++------- compiler/rustc_middle/src/ty/assoc.rs | 8 +++- .../src/traits/dyn_compatibility.rs | 4 +- .../dyn-compat-non-type-assoc-const.rs | 9 ++-- .../dyn-compat-non-type-assoc-const.stderr | 48 +++++-------------- .../mgca/dyn-non-type-assoc-const-binding.rs | 1 - .../dyn-non-type-assoc-const-binding.stderr | 45 ----------------- .../type_const-only-in-impl-omitted-type.rs | 1 - ...ype_const-only-in-impl-omitted-type.stderr | 10 +--- .../mgca/type_const-only-in-impl.rs | 3 +- .../mgca/type_const-only-in-impl.stderr | 10 ---- tests/ui/type-alias/lack-of-wfcheck.rs | 4 -- 13 files changed, 35 insertions(+), 140 deletions(-) delete mode 100644 tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr delete mode 100644 tests/ui/const-generics/mgca/type_const-only-in-impl.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 1a103b9db1fc0..5cf13b51a7a8c 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 @@ -1016,8 +1016,9 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { // that would result in invalid syntax (fixes #116464) if !self.is_in_trait_impl() { let unused_generics = &self.gen_args.args[self.num_expected_type_or_const_args()..]; - let mut unbound_assoc_consts = - unbound_assoc_items.iter().filter(|item| item.is_type_const()); + let mut unbound_assoc_consts = unbound_assoc_items + .iter() + .filter(|item| matches!(item.kind, ty::AssocKind::Const { .. })); let mut unbound_assoc_types = unbound_assoc_items.iter().filter(|item| item.is_type()); let suggestions = unused_generics diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index d730683b132d4..95f3e66e234f9 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -552,26 +552,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) }); - if let ty::AssocTag::Const = assoc_tag - && !self.tcx().is_type_const(assoc_item.def_id) - { - if tcx.features().min_generic_const_args() { - let mut err = self.dcx().struct_span_err( - constraint.span, - "use of trait associated const not defined as `type const`", - ); - err.note("the declaration in the trait must begin with `type const` not just `const` alone"); - return Err(err.emit()); - } else { - let err = self.dcx().span_delayed_bug( - constraint.span, - "use of trait associated const defined as `type const`", - ); - return Err(err); - } - } else { - bounds.push((bound.upcast(tcx), constraint.span)); + if !assoc_item.can_have_equality_constraint(tcx) { + let err = self.dcx().span_delayed_bug( + constraint.span, + "associated item does not support equality constraints", + ); + return Err(err); } + + bounds.push((bound.upcast(tcx), constraint.span)); } // SelfTraitThatDefines is only interested in trait predicates. PredicateFilter::SelfTraitThatDefines(_) => {} diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 4999e6426f434..da4100fd20611 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -143,8 +143,12 @@ impl AssocItem { } /// Whether this associated item can be constrained with an equality binding. - pub fn can_have_equality_constraint(&self, _tcx: TyCtxt<'_>) -> bool { - self.is_type() || self.is_type_const() + pub fn can_have_equality_constraint(&self, tcx: TyCtxt<'_>) -> bool { + match self.kind { + ty::AssocKind::Type { .. } => true, + ty::AssocKind::Const { .. } => tcx.features().min_generic_const_args(), + ty::AssocKind::Fn { .. } => false, + } } pub fn is_fn(&self) -> bool { diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 7b45dfe48a7fe..ef61099cb6301 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -360,7 +360,7 @@ pub fn dyn_compatibility_violations_for_assoc_item( let span = || item.ident(tcx).span; match item.kind { - ty::AssocKind::Const { name, is_type_const } => { + ty::AssocKind::Const { name, .. } => { // We will permit type associated consts if they are explicitly mentioned in the // trait object type. We can't check this here, as here we only check if it is // guaranteed to not be possible. @@ -370,8 +370,6 @@ pub fn dyn_compatibility_violations_for_assoc_item( if tcx.features().min_generic_const_args() { if !tcx.generics_of(item.def_id).is_own_empty() { errors.push(AssocConstViolation::Generic); - } else if !is_type_const { - errors.push(AssocConstViolation::NonType); } let ty = ty::Binder::dummy( diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs index 38d593984724e..0482950e77211 100644 --- a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs @@ -1,4 +1,4 @@ -// Ensure that traits with non-type associated consts are dyn *in*compatible. +// Ensure that traits with non-type associated consts require explicit dyn bindings. //@ dont-require-annotations: NOTE @@ -7,14 +7,11 @@ trait Trait { const K: usize; - //~^ NOTE it contains associated const `K` that's not defined as `type const` } fn main() { - let _: dyn Trait; //~ ERROR the trait `Trait` is not dyn compatible + let _: dyn Trait; //~ ERROR the value of the associated constant `K` in `Trait` must be specified - // Check that specifying the non-type assoc const doesn't "magically make it work". + // Specifying the non-type assoc const makes the dyn type fully constrained. let _: dyn Trait; - //~^ ERROR the trait `Trait` is not dyn compatible - //~| ERROR use of trait associated const not defined as `type const` } diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr index 5bc072e98c0f8..72aca31e5ace0 100644 --- a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr @@ -1,43 +1,17 @@ -error[E0038]: the trait `Trait` is not dyn compatible - --> $DIR/dyn-compat-non-type-assoc-const.rs:14:16 +error[E0191]: the value of the associated constant `K` in `Trait` must be specified + --> $DIR/dyn-compat-non-type-assoc-const.rs:13:16 | -LL | let _: dyn Trait; - | ^^^^^ `Trait` is not dyn compatible - | -note: for a trait to be dyn compatible it needs to allow building a vtable - for more information, visit - --> $DIR/dyn-compat-non-type-assoc-const.rs:9:11 - | -LL | trait Trait { - | ----- this trait is not dyn compatible... LL | const K: usize; - | ^ ...because it contains associated const `K` that's not defined as `type const` - = help: consider moving `K` to another trait - -error: use of trait associated const not defined as `type const` - --> $DIR/dyn-compat-non-type-assoc-const.rs:17:22 - | -LL | let _: dyn Trait; - | ^^^^^ - | - = note: the declaration in the trait must begin with `type const` not just `const` alone - -error[E0038]: the trait `Trait` is not dyn compatible - --> $DIR/dyn-compat-non-type-assoc-const.rs:17:16 - | -LL | let _: dyn Trait; - | ^^^^^^^^^^^^ `Trait` is not dyn compatible + | -------------- `K` defined here +... +LL | let _: dyn Trait; + | ^^^^^ | -note: for a trait to be dyn compatible it needs to allow building a vtable - for more information, visit - --> $DIR/dyn-compat-non-type-assoc-const.rs:9:11 +help: specify the associated constant | -LL | trait Trait { - | ----- this trait is not dyn compatible... -LL | const K: usize; - | ^ ...because it contains associated const `K` that's not defined as `type const` - = help: consider moving `K` to another trait +LL | let _: dyn Trait; + | +++++++++++++++++ -error: aborting due to 3 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0038`. +For more information about this error, try `rustc --explain E0191`. diff --git a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs index b683af7973913..bf764b829ab6d 100644 --- a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs +++ b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs @@ -1,6 +1,5 @@ //@ check-pass //@ compile-flags: -Znext-solver=globally -//@ dont-require-annotations: NOTE #![feature(min_generic_const_args, generic_const_args)] #![expect(incomplete_features)] diff --git a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr b/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr deleted file mode 100644 index 05a7fd4fa3b4f..0000000000000 --- a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.stderr +++ /dev/null @@ -1,45 +0,0 @@ -error: use of trait associated const not defined as `type const` - --> $DIR/dyn-non-type-assoc-const-binding.rs:12:22 - | -LL | fn foo(_: &dyn Trait) {} - | ^^^^^^^^^^ - | - = note: the declaration in the trait must begin with `type const` not just `const` alone - -error: use of trait associated const not defined as `type const` - --> $DIR/dyn-non-type-assoc-const-binding.rs:12:22 - | -LL | fn foo(_: &dyn Trait) {} - | ^^^^^^^^^^ - | - = note: the declaration in the trait must begin with `type const` not just `const` alone - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error: use of trait associated const not defined as `type const` - --> $DIR/dyn-non-type-assoc-const-binding.rs:12:22 - | -LL | fn foo(_: &dyn Trait) {} - | ^^^^^^^^^^ - | - = note: the declaration in the trait must begin with `type const` not just `const` alone - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error[E0038]: the trait `Trait` is not dyn compatible - --> $DIR/dyn-non-type-assoc-const-binding.rs:12:12 - | -LL | fn foo(_: &dyn Trait) {} - | ^^^^^^^^^^^^^^^^^^^^^ `Trait` is not dyn compatible - | -note: for a trait to be dyn compatible it needs to allow building a vtable - for more information, visit - --> $DIR/dyn-non-type-assoc-const-binding.rs:9:11 - | -LL | trait Trait { - | ----- this trait is not dyn compatible... -LL | const ASSOC: usize; - | ^^^^^ ...because it contains associated const `ASSOC` that's not defined as `type const` - = help: consider moving `ASSOC` to another trait - -error: aborting due to 4 previous errors - -For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs index c8c7788eb135a..4f9ada5382dcd 100644 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs @@ -15,7 +15,6 @@ impl BadTr for GoodS { } fn accept_bad_tr>(_x: &T) {} -//~^ ERROR use of trait associated const not defined as `type const` fn main() { accept_bad_tr::<84, _>(&GoodS); diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr index 99dc6398170db..0997e6dd9d847 100644 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr @@ -4,19 +4,11 @@ error: missing type for `const` item LL | type const NUM: = 84; | ^ help: provide a type for the associated constant: `usize` -error: use of trait associated const not defined as `type const` - --> $DIR/type_const-only-in-impl-omitted-type.rs:17:43 - | -LL | fn accept_bad_tr>(_x: &T) {} - | ^^^^^^^^^^^ - | - = note: the declaration in the trait must begin with `type const` not just `const` alone - error: type annotations needed for the literal --> $DIR/type_const-only-in-impl-omitted-type.rs:11:23 | LL | type const NUM: = 84; | ^^ -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl.rs b/tests/ui/const-generics/mgca/type_const-only-in-impl.rs index e016908b3cc38..7abe9cac0396d 100644 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl.rs +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl.rs @@ -1,3 +1,5 @@ +//@ check-pass + #![expect(incomplete_features)] #![feature(min_generic_const_args)] @@ -12,7 +14,6 @@ impl BadTr for GoodS { } fn accept_bad_tr>(_x: &T) {} -//~^ ERROR use of trait associated const not defined as `type const` fn main() { accept_bad_tr::<84, _>(&GoodS); diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl.stderr b/tests/ui/const-generics/mgca/type_const-only-in-impl.stderr deleted file mode 100644 index 55d5cca6ba699..0000000000000 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: use of trait associated const not defined as `type const` - --> $DIR/type_const-only-in-impl.rs:14:43 - | -LL | fn accept_bad_tr>(_x: &T) {} - | ^^^^^^^^^^^ - | - = note: the declaration in the trait must begin with `type const` not just `const` alone - -error: aborting due to 1 previous error - diff --git a/tests/ui/type-alias/lack-of-wfcheck.rs b/tests/ui/type-alias/lack-of-wfcheck.rs index 91fbee8d3f198..2f47986a5d6f9 100644 --- a/tests/ui/type-alias/lack-of-wfcheck.rs +++ b/tests/ui/type-alias/lack-of-wfcheck.rs @@ -14,14 +14,10 @@ type UnsatOutlivesBound<'a> = &'static &'a (); // `'a: 'static` unsatisfied type Diverging = [(); panic!()]; // `panic!()` diverging type DynIncompat0 = dyn Sized; // `Sized` axiomatically dyn incompatible -// issue: -type DynIncompat1 = dyn HasAssocConst; // dyn incompatible due to (non-type-level) assoc const - // * dyn incompatible due to GAT // * `'a: 'static`, `String: Copy` and `[u8]: Sized` unsatisfied, `loop {}` diverging type Several<'a> = dyn HasGenericAssocType = [u8]>; -trait HasAssocConst { const N: usize; } trait HasGenericAssocType { type Type<'a: 'static, T: Copy, const N: usize>; } fn main() {} From cd57bc3ab4f5ba7f2c5034d9334f1a91d2458a02 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 20 Jul 2026 13:24:03 +0000 Subject: [PATCH 06/42] Correct the statments --- compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs | 2 -- compiler/rustc_middle/src/ty/assoc.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index 482d92759b886..10f0cb31e7b45 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -231,8 +231,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ordered_associated_items.extend( tcx.associated_items(pred.trait_ref.def_id) .in_definition_order() - // Only associated items that support equality constraints can - // be constrained in a trait object type via a binding. .filter(|item| item.can_have_equality_constraint(tcx)) // Traits with RPITITs are simply not dyn compatible (for now). .filter(|item| !item.is_impl_trait_in_trait()) diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index da4100fd20611..8f4bcde3eee03 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -142,7 +142,7 @@ impl AssocItem { matches!(self.kind, ty::AssocKind::Const { is_type_const: true, .. }) } - /// Whether this associated item can be constrained with an equality binding. + /// Whether this associated item can be constrained with an equality constraint. pub fn can_have_equality_constraint(&self, tcx: TyCtxt<'_>) -> bool { match self.kind { ty::AssocKind::Type { .. } => true, From faebca4a223d4ed6f1c470091257246e7fc7ce62 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 20 Jul 2026 13:34:51 +0000 Subject: [PATCH 07/42] Remove NonType assocConstViolation variant, as we don't need it anymore --- compiler/rustc_middle/src/traits/mod.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 58a8eeeb242c0..f81146416453f 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -840,10 +840,6 @@ impl DynCompatibilityViolation { Self::AssocConst(name, AssocConstViolation::Generic, _) => { format!("it contains generic associated const `{name}`").into() } - Self::AssocConst(name, AssocConstViolation::NonType, _) => { - format!("it contains associated const `{name}` that's not defined as `type const`") - .into() - } Self::AssocConst(name, AssocConstViolation::TypeReferencesSelf, _) => format!( "it contains associated const `{name}` whose type references the `Self` type" ) @@ -995,9 +991,6 @@ pub enum AssocConstViolation { /// Has own generic parameters (GAC). Generic, - /// Isn't defined as `type const`. - NonType, - /// Its type mentions the `Self` type parameter. TypeReferencesSelf, } From a8087a3a266754ea70142d6426100f9a50f70182 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 21 Jul 2026 17:38:32 +0000 Subject: [PATCH 08/42] Gate non-type assoc const equality on generic_const_args --- .../src/hir_ty_lowering/bounds.rs | 26 +++++++--- compiler/rustc_middle/src/traits/mod.rs | 7 +++ compiler/rustc_middle/src/ty/assoc.rs | 7 ++- .../src/traits/dyn_compatibility.rs | 4 +- .../dyn-compat-non-type-assoc-const.rs | 9 ++-- .../dyn-compat-non-type-assoc-const.stderr | 48 ++++++++++++++----- .../dyn-non-type-assoc-const-binding.rs | 0 .../type_const-only-in-impl-omitted-type.rs | 1 + ...ype_const-only-in-impl-omitted-type.stderr | 10 +++- .../mgca/type_const-only-in-impl.rs | 3 +- .../mgca/type_const-only-in-impl.stderr | 10 ++++ tests/ui/type-alias/lack-of-wfcheck.rs | 4 ++ 12 files changed, 103 insertions(+), 26 deletions(-) rename tests/ui/const-generics/{mgca => gca}/dyn-non-type-assoc-const-binding.rs (100%) create mode 100644 tests/ui/const-generics/mgca/type_const-only-in-impl.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 95f3e66e234f9..477cf1b9b7e4f 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -552,12 +552,26 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) }); - if !assoc_item.can_have_equality_constraint(tcx) { - let err = self.dcx().span_delayed_bug( - constraint.span, - "associated item does not support equality constraints", - ); - return Err(err); + if let ty::AssocTag::Const = assoc_tag + && !self.tcx().is_type_const(assoc_item.def_id) + && !tcx.features().generic_const_args() + { + if tcx.features().min_generic_const_args() { + let mut err = self.dcx().struct_span_err( + constraint.span, + "use of trait associated const not defined as `type const`", + ); + err.note( + "the declaration in the trait must begin with `type const` not just `const` alone", + ); + return Err(err.emit()); + } else { + let err = self.dcx().span_delayed_bug( + constraint.span, + "use of trait associated const defined as `type const`", + ); + return Err(err); + } } bounds.push((bound.upcast(tcx), constraint.span)); diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index f81146416453f..b1095cb4bcaa7 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -837,6 +837,10 @@ impl DynCompatibilityViolation { Self::AssocConst(name, AssocConstViolation::FeatureNotEnabled, _) => { format!("it contains associated const `{name}`").into() } + Self::AssocConst(name, AssocConstViolation::NonType, _) => { + format!("it contains associated const `{name}` that's not defined as `type const`") + .into() + } Self::AssocConst(name, AssocConstViolation::Generic, _) => { format!("it contains generic associated const `{name}`").into() } @@ -988,6 +992,9 @@ pub enum AssocConstViolation { /// Unstable feature `min_generic_const_args` wasn't enabled. FeatureNotEnabled, + /// Not defined as a type-level associated const. + NonType, + /// Has own generic parameters (GAC). Generic, diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 8f4bcde3eee03..279a3658109bc 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -142,11 +142,14 @@ impl AssocItem { matches!(self.kind, ty::AssocKind::Const { is_type_const: true, .. }) } - /// Whether this associated item can be constrained with an equality constraint. + /// Whether this associated item can be constrained with an equality binding. pub fn can_have_equality_constraint(&self, tcx: TyCtxt<'_>) -> bool { match self.kind { ty::AssocKind::Type { .. } => true, - ty::AssocKind::Const { .. } => tcx.features().min_generic_const_args(), + ty::AssocKind::Const { is_type_const: true, .. } => true, + ty::AssocKind::Const { is_type_const: false, .. } => { + tcx.features().generic_const_args() + } ty::AssocKind::Fn { .. } => false, } } diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index ef61099cb6301..7843ec47bf292 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -360,7 +360,7 @@ pub fn dyn_compatibility_violations_for_assoc_item( let span = || item.ident(tcx).span; match item.kind { - ty::AssocKind::Const { name, .. } => { + ty::AssocKind::Const { name, is_type_const } => { // We will permit type associated consts if they are explicitly mentioned in the // trait object type. We can't check this here, as here we only check if it is // guaranteed to not be possible. @@ -370,6 +370,8 @@ pub fn dyn_compatibility_violations_for_assoc_item( if tcx.features().min_generic_const_args() { if !tcx.generics_of(item.def_id).is_own_empty() { errors.push(AssocConstViolation::Generic); + } else if !is_type_const && !tcx.features().generic_const_args() { + errors.push(AssocConstViolation::NonType); } let ty = ty::Binder::dummy( diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs index 0482950e77211..302c4e4187349 100644 --- a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.rs @@ -1,4 +1,4 @@ -// Ensure that traits with non-type associated consts require explicit dyn bindings. +// Ensure that traits with non-type associated consts are dyn *in*compatible. //@ dont-require-annotations: NOTE @@ -7,11 +7,14 @@ trait Trait { const K: usize; + //~^ NOTE it contains associated const `K` that's not defined as `type const` } fn main() { - let _: dyn Trait; //~ ERROR the value of the associated constant `K` in `Trait` must be specified + let _: dyn Trait; //~ ERROR the trait `Trait` is not dyn compatible - // Specifying the non-type assoc const makes the dyn type fully constrained. + // Check that specifying the non-type assoc const doesn't work without full GCA. let _: dyn Trait; + //~^ ERROR the trait `Trait` is not dyn compatible + //~| ERROR use of trait associated const not defined as `type const` } diff --git a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr index 72aca31e5ace0..5bc072e98c0f8 100644 --- a/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr +++ b/tests/ui/const-generics/associated-const-bindings/dyn-compat-non-type-assoc-const.stderr @@ -1,17 +1,43 @@ -error[E0191]: the value of the associated constant `K` in `Trait` must be specified - --> $DIR/dyn-compat-non-type-assoc-const.rs:13:16 +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-non-type-assoc-const.rs:14:16 | -LL | const K: usize; - | -------------- `K` defined here -... LL | let _: dyn Trait; - | ^^^^^ + | ^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-non-type-assoc-const.rs:9:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const K: usize; + | ^ ...because it contains associated const `K` that's not defined as `type const` + = help: consider moving `K` to another trait + +error: use of trait associated const not defined as `type const` + --> $DIR/dyn-compat-non-type-assoc-const.rs:17:22 | -help: specify the associated constant +LL | let _: dyn Trait; + | ^^^^^ | -LL | let _: dyn Trait; - | +++++++++++++++++ + = note: the declaration in the trait must begin with `type const` not just `const` alone + +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-non-type-assoc-const.rs:17:16 + | +LL | let _: dyn Trait; + | ^^^^^^^^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-non-type-assoc-const.rs:9:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const K: usize; + | ^ ...because it contains associated const `K` that's not defined as `type const` + = help: consider moving `K` to another trait -error: aborting due to 1 previous error +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0191`. +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs b/tests/ui/const-generics/gca/dyn-non-type-assoc-const-binding.rs similarity index 100% rename from tests/ui/const-generics/mgca/dyn-non-type-assoc-const-binding.rs rename to tests/ui/const-generics/gca/dyn-non-type-assoc-const-binding.rs diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs index 4f9ada5382dcd..c8c7788eb135a 100644 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.rs @@ -15,6 +15,7 @@ impl BadTr for GoodS { } fn accept_bad_tr>(_x: &T) {} +//~^ ERROR use of trait associated const not defined as `type const` fn main() { accept_bad_tr::<84, _>(&GoodS); diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr index 0997e6dd9d847..99dc6398170db 100644 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl-omitted-type.stderr @@ -4,11 +4,19 @@ error: missing type for `const` item LL | type const NUM: = 84; | ^ help: provide a type for the associated constant: `usize` +error: use of trait associated const not defined as `type const` + --> $DIR/type_const-only-in-impl-omitted-type.rs:17:43 + | +LL | fn accept_bad_tr>(_x: &T) {} + | ^^^^^^^^^^^ + | + = note: the declaration in the trait must begin with `type const` not just `const` alone + error: type annotations needed for the literal --> $DIR/type_const-only-in-impl-omitted-type.rs:11:23 | LL | type const NUM: = 84; | ^^ -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl.rs b/tests/ui/const-generics/mgca/type_const-only-in-impl.rs index 7abe9cac0396d..e016908b3cc38 100644 --- a/tests/ui/const-generics/mgca/type_const-only-in-impl.rs +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl.rs @@ -1,5 +1,3 @@ -//@ check-pass - #![expect(incomplete_features)] #![feature(min_generic_const_args)] @@ -14,6 +12,7 @@ impl BadTr for GoodS { } fn accept_bad_tr>(_x: &T) {} +//~^ ERROR use of trait associated const not defined as `type const` fn main() { accept_bad_tr::<84, _>(&GoodS); diff --git a/tests/ui/const-generics/mgca/type_const-only-in-impl.stderr b/tests/ui/const-generics/mgca/type_const-only-in-impl.stderr new file mode 100644 index 0000000000000..55d5cca6ba699 --- /dev/null +++ b/tests/ui/const-generics/mgca/type_const-only-in-impl.stderr @@ -0,0 +1,10 @@ +error: use of trait associated const not defined as `type const` + --> $DIR/type_const-only-in-impl.rs:14:43 + | +LL | fn accept_bad_tr>(_x: &T) {} + | ^^^^^^^^^^^ + | + = note: the declaration in the trait must begin with `type const` not just `const` alone + +error: aborting due to 1 previous error + diff --git a/tests/ui/type-alias/lack-of-wfcheck.rs b/tests/ui/type-alias/lack-of-wfcheck.rs index 2f47986a5d6f9..91fbee8d3f198 100644 --- a/tests/ui/type-alias/lack-of-wfcheck.rs +++ b/tests/ui/type-alias/lack-of-wfcheck.rs @@ -14,10 +14,14 @@ type UnsatOutlivesBound<'a> = &'static &'a (); // `'a: 'static` unsatisfied type Diverging = [(); panic!()]; // `panic!()` diverging type DynIncompat0 = dyn Sized; // `Sized` axiomatically dyn incompatible +// issue: +type DynIncompat1 = dyn HasAssocConst; // dyn incompatible due to (non-type-level) assoc const + // * dyn incompatible due to GAT // * `'a: 'static`, `String: Copy` and `[u8]: Sized` unsatisfied, `loop {}` diverging type Several<'a> = dyn HasGenericAssocType = [u8]>; +trait HasAssocConst { const N: usize; } trait HasGenericAssocType { type Type<'a: 'static, T: Copy, const N: usize>; } fn main() {} From 407749626ede9b0041b9a1387dec808e385f9cf9 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 23 Jul 2026 12:57:48 +0000 Subject: [PATCH 09/42] add dyn generic non type assoc const --- ...dyn-compat-generic-non-type-assoc-const.rs | 18 ++++++++++++++++++ ...compat-generic-non-type-assoc-const.stderr | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.rs create mode 100644 tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.stderr diff --git a/tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.rs b/tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.rs new file mode 100644 index 0000000000000..ec32c7c8b1062 --- /dev/null +++ b/tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.rs @@ -0,0 +1,18 @@ +// Ensure that traits with generic non-type associated consts are dyn *in*compatible, +// even when non-type associated const equality is enabled by `generic_const_args`. + +//@ dont-require-annotations: NOTE +//@ compile-flags: -Znext-solver=globally + +#![feature(generic_const_args, generic_const_items, min_generic_const_args)] +#![expect(incomplete_features)] + +trait Trait { + const ASSOC: usize; + //~^ NOTE it contains generic associated const `ASSOC` +} + +fn main() { + let _: dyn Trait; + //~^ ERROR the trait `Trait` is not dyn compatible +} diff --git a/tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.stderr b/tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.stderr new file mode 100644 index 0000000000000..ff09f40f7ca98 --- /dev/null +++ b/tests/ui/const-generics/gca/dyn-compat-generic-non-type-assoc-const.stderr @@ -0,0 +1,19 @@ +error[E0038]: the trait `Trait` is not dyn compatible + --> $DIR/dyn-compat-generic-non-type-assoc-const.rs:16:16 + | +LL | let _: dyn Trait; + | ^^^^^ `Trait` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/dyn-compat-generic-non-type-assoc-const.rs:11:11 + | +LL | trait Trait { + | ----- this trait is not dyn compatible... +LL | const ASSOC: usize; + | ^^^^^ ...because it contains generic associated const `ASSOC` + = help: consider moving `ASSOC` to another trait + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0038`. From ad7e5e25757fac115d28984b786def6df009d10d Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 23 Jul 2026 12:58:03 +0000 Subject: [PATCH 10/42] add lack_of_wfcheck for gca --- .../lack-of-wfcheck-generic-const-args.rs | 31 +++++++++++++++++++ .../lack-of-wfcheck-generic-const-args.stderr | 17 ++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs create mode 100644 tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr diff --git a/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs new file mode 100644 index 0000000000000..38697ccf5d54c --- /dev/null +++ b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs @@ -0,0 +1,31 @@ +// Demonstrate that generic_const_args changes the behavior for dyn trait aliases +// with non-type associated consts: the associated const must be specified. + +//@ compile-flags: -Znext-solver=globally + +#![feature(generic_const_args, min_generic_const_args)] +#![expect(incomplete_features)] + +type UnsatTraitBound0 = [str]; // `str: Sized` unsatisfied +type UnsatTraitBound1> = T; // `str: Sized` unsatisfied +type UnsatOutlivesBound<'a> = &'static &'a (); // `'a: 'static` unsatisfied + +type Diverging = [(); panic!()]; // `panic!()` diverging + +type DynIncompat0 = dyn Sized; // `Sized` axiomatically dyn incompatible +// issue: +type DynIncompat1 = dyn HasAssocConst; +//~^ ERROR the value of the associated constant `N` in `HasAssocConst` must be specified + +// * dyn incompatible due to GAT +// * `'a: 'static`, `String: Copy` and `[u8]: Sized` unsatisfied, `loop {}` diverging +type Several<'a> = dyn HasGenericAssocType = [u8]>; + +trait HasAssocConst { + const N: usize; +} +trait HasGenericAssocType { + type Type<'a: 'static, T: Copy, const N: usize>; +} + +fn main() {} diff --git a/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr new file mode 100644 index 0000000000000..63f10e69bd0f5 --- /dev/null +++ b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr @@ -0,0 +1,17 @@ +error[E0191]: the value of the associated constant `N` in `HasAssocConst` must be specified + --> $DIR/lack-of-wfcheck-generic-const-args.rs:17:25 + | +LL | type DynIncompat1 = dyn HasAssocConst; + | ^^^^^^^^^^^^^ +... +LL | const N: usize; + | -------------- `N` defined here + | +help: specify the associated constant + | +LL | type DynIncompat1 = dyn HasAssocConst; + | +++++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0191`. From 2191fc155571ae14ceeb6ba685b29211e7ef8cb4 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 25 Jul 2026 06:23:27 +0000 Subject: [PATCH 11/42] Revise generic const args WF test and split GAT case --- .../lack-of-wfcheck-gat-generic-const-args.rs | 21 ++++++++++++ ...k-of-wfcheck-gat-generic-const-args.stderr | 34 +++++++++++++++++++ ...-of-wfcheck-generic-const-args.gca.stderr} | 2 +- .../lack-of-wfcheck-generic-const-args.rs | 15 +++----- 4 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.rs create mode 100644 tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.stderr rename tests/ui/type-alias/{lack-of-wfcheck-generic-const-args.stderr => lack-of-wfcheck-generic-const-args.gca.stderr} (90%) diff --git a/tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.rs b/tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.rs new file mode 100644 index 0000000000000..58bc4daedfcc1 --- /dev/null +++ b/tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.rs @@ -0,0 +1,21 @@ +// Demonstrate that generic const arguments in GAT constraints are rejected at +// the definition site of an eager type alias. + +//@ compile-flags: -Znext-solver=globally + +#![feature(generic_const_args, min_generic_const_args)] +#![expect(incomplete_features)] + +// * dyn incompatible due to GAT +// * `'a: 'static`, `String: Copy` and `[u8]: Sized` unsatisfied, `loop {}` diverging +type Several<'a> = dyn HasGenericAssocType = [u8]>; +//~^ ERROR + +trait HasGenericAssocType { + type Type<'a: 'static, T: Copy, const N: usize>; +} + +fn main() { + let _: &Several<'_>; + //~^ ERROR the trait `HasGenericAssocType` is not dyn compatible +} diff --git a/tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.stderr b/tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.stderr new file mode 100644 index 0000000000000..6b06ba9cb14fe --- /dev/null +++ b/tests/ui/type-alias/lack-of-wfcheck-gat-generic-const-args.stderr @@ -0,0 +1,34 @@ +error: constant evaluation is taking a long time + --> $DIR/lack-of-wfcheck-gat-generic-const-args.rs:11:63 + | +LL | type Several<'a> = dyn HasGenericAssocType = [u8]>; + | ^^^^^^^ + | + = note: this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. + If your compilation actually takes a long time, you can safely allow the lint +help: the constant being evaluated + --> $DIR/lack-of-wfcheck-gat-generic-const-args.rs:11:61 + | +LL | type Several<'a> = dyn HasGenericAssocType = [u8]>; + | ^^^^^^^^^^^ + = note: `#[deny(long_running_const_eval)]` on by default + +error[E0038]: the trait `HasGenericAssocType` is not dyn compatible + --> $DIR/lack-of-wfcheck-gat-generic-const-args.rs:19:12 + | +LL | let _: &Several<'_>; + | ^^^^^^^^^^^^ `HasGenericAssocType` is not dyn compatible + | +note: for a trait to be dyn compatible it needs to allow building a vtable + for more information, visit + --> $DIR/lack-of-wfcheck-gat-generic-const-args.rs:15:10 + | +LL | trait HasGenericAssocType { + | ------------------- this trait is not dyn compatible... +LL | type Type<'a: 'static, T: Copy, const N: usize>; + | ^^^^ ...because it contains generic associated type `Type` + = help: consider moving `Type` to another trait + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0038`. diff --git a/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.gca.stderr similarity index 90% rename from tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr rename to tests/ui/type-alias/lack-of-wfcheck-generic-const-args.gca.stderr index 63f10e69bd0f5..52edd50aaaaad 100644 --- a/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.stderr +++ b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.gca.stderr @@ -1,5 +1,5 @@ error[E0191]: the value of the associated constant `N` in `HasAssocConst` must be specified - --> $DIR/lack-of-wfcheck-generic-const-args.rs:17:25 + --> $DIR/lack-of-wfcheck-generic-const-args.rs:19:25 | LL | type DynIncompat1 = dyn HasAssocConst; | ^^^^^^^^^^^^^ diff --git a/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs index 38697ccf5d54c..afca550944ffc 100644 --- a/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs +++ b/tests/ui/type-alias/lack-of-wfcheck-generic-const-args.rs @@ -1,10 +1,12 @@ // Demonstrate that generic_const_args changes the behavior for dyn trait aliases // with non-type associated consts: the associated const must be specified. +//@ revisions: no_gca gca //@ compile-flags: -Znext-solver=globally +//@ [no_gca] check-pass -#![feature(generic_const_args, min_generic_const_args)] -#![expect(incomplete_features)] +#![cfg_attr(gca, feature(generic_const_args, min_generic_const_args))] +#![cfg_attr(gca, expect(incomplete_features))] type UnsatTraitBound0 = [str]; // `str: Sized` unsatisfied type UnsatTraitBound1> = T; // `str: Sized` unsatisfied @@ -15,17 +17,10 @@ type Diverging = [(); panic!()]; // `panic!()` diverging type DynIncompat0 = dyn Sized; // `Sized` axiomatically dyn incompatible // issue: type DynIncompat1 = dyn HasAssocConst; -//~^ ERROR the value of the associated constant `N` in `HasAssocConst` must be specified - -// * dyn incompatible due to GAT -// * `'a: 'static`, `String: Copy` and `[u8]: Sized` unsatisfied, `loop {}` diverging -type Several<'a> = dyn HasGenericAssocType = [u8]>; +//[gca]~^ ERROR the value of the associated constant `N` in `HasAssocConst` must be specified trait HasAssocConst { const N: usize; } -trait HasGenericAssocType { - type Type<'a: 'static, T: Copy, const N: usize>; -} fn main() {} From 844c01e43be5782643646d73a6f65539db046a33 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:14:14 +0000 Subject: [PATCH 12/42] Cover memchr fast path with bounds assertion --- library/core/src/slice/memchr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index c83e8b218da08..017661f0448c2 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,13 +24,13 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - return memchr_naive(x, text); - } - - let result = memchr_aligned(x, text); + let result = if text.len() < 2 * USIZE_BYTES { + memchr_naive(x, text) + } else { + memchr_aligned(x, text) + }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From 49c1f02279a37b85fcd9448dc7b87e20923f57dd Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:32:44 +0000 Subject: [PATCH 13/42] Fix memchr result CI checks --- library/core/src/slice/memchr.rs | 7 ++----- tests/codegen-llvm/lib-optimizations/memchr-result.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 017661f0448c2..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,11 +24,8 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = if text.len() < 2 * USIZE_BYTES { - memchr_naive(x, text) - } else { - memchr_aligned(x, text) - }; + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index f18335075451c..77abc33adde83 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,6 +1,6 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-64bit +//@ only-x86_64 #![crate_type = "lib"] #![feature(slice_internals)] From 807750a1fcdb31f4bf527089ff44cf95ac199046 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:57:45 +0000 Subject: [PATCH 14/42] Preserve memchr codegen on LLVM 21 --- library/core/src/slice/memchr.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index fb99e86139d7e..68826ecac31f3 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,10 +24,18 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = - if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; + if text.len() < 2 * USIZE_BYTES { + let result = memchr_naive(x, text); + if let Some(index) = result { + // SAFETY: `memchr_naive` only returns an index from within `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + return result; + } + + let result = memchr_aligned(x, text); if let Some(index) = result { - // SAFETY: Both implementations only return an index from within `text`. + // SAFETY: `memchr_aligned` only returns an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From bdbfee00615abcd0009f22b9c52f5df65023eb9c Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 3 Aug 2026 13:11:14 +0800 Subject: [PATCH 15/42] Split `aarch64-apple{,-macos-26}` => `aarch64-apple{,-macos-26}-{1,2}` jobs The aarch64 macos runners seem to be consistently among the slowest jobs, sometimes pushing our overall CI time to 4 hours on a bad run. Let's try to split the jobs to keep the overall Merge CI time manageable: * `aarch64-apple` => `aarch64-apple-{1,2}` * `aarch64-apple-macos-26` => `aarch64-apple-macos-26-{1,2}` --- src/ci/docker/scripts/stage_2_test_set1.sh | 2 + src/ci/docker/scripts/stage_2_test_set2.sh | 2 + src/ci/github-actions/jobs.yml | 77 +++++++++++++++++++--- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/ci/docker/scripts/stage_2_test_set1.sh b/src/ci/docker/scripts/stage_2_test_set1.sh index e7930513c0d62..62b3c2c051a40 100755 --- a/src/ci/docker/scripts/stage_2_test_set1.sh +++ b/src/ci/docker/scripts/stage_2_test_set1.sh @@ -4,6 +4,8 @@ set -ex # Run a subset of tests. Used to run tests in parallel in multiple jobs. +# NOTE: keep in sync with `aarch64-apple*-{1,2}` jobs. + # When this job partition is run as part of PR CI, skip tidy to allow revealing more failures. The # dedicated `tidy` job failing won't block other PR CI jobs from completing, and so tidy failures # shouldn't inhibit revealing other failures in PR CI jobs. diff --git a/src/ci/docker/scripts/stage_2_test_set2.sh b/src/ci/docker/scripts/stage_2_test_set2.sh index 5963924cce529..c0cdc31011378 100755 --- a/src/ci/docker/scripts/stage_2_test_set2.sh +++ b/src/ci/docker/scripts/stage_2_test_set2.sh @@ -4,6 +4,8 @@ set -ex # Run a subset of tests. Used to run tests in parallel in multiple jobs. +# NOTE: keep in sync with `aarch64-apple*-{1,2}` jobs. + # When this job partition is run as part of PR CI, skip tidy to allow revealing more failures. The # dedicated `tidy` job failing won't block other PR CI jobs from completing, and so tidy failures # shouldn't inhibit revealing other failures in PR CI jobs. diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml index 1451b633986b9..0166fa599184d 100644 --- a/src/ci/github-actions/jobs.yml +++ b/src/ci/github-actions/jobs.yml @@ -585,11 +585,41 @@ auto: CODEGEN_BACKENDS: llvm,cranelift <<: *job-macos-15 - - name: aarch64-apple + - name: aarch64-apple-1 env: - SCRIPT: > - ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin && - ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin src/tools/cargo + # NOTE: keep in sync with `src/ci/docker/scripts/stage_2_test_set1.sh` + SCRIPT: >- + ./x.py --stage 2 test + --host=aarch64-apple-darwin + --target=aarch64-apple-darwin + --skip compiler + --skip src + RUST_CONFIGURE_ARGS: >- + --enable-sanitizers + --enable-profiler + --set build.allocator=jemalloc + DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer + # Aarch64 tooling only needs to support macOS 11.0 and up as nothing else + # supports the hardware, so only need to test it there. + MACOSX_DEPLOYMENT_TARGET: 11.0 + MACOSX_STD_DEPLOYMENT_TARGET: 11.0 + <<: *job-macos-15 + + - name: aarch64-apple-2 + env: + # NOTE: keep in sync with `src/ci/docker/scripts/stage_2_test_set2.sh`, + # union `src/tools/cargo` specifically. + SCRIPT: >- + ./x.py --stage 2 test + --host=aarch64-apple-darwin + --target=aarch64-apple-darwin + --skip tests + --skip library + --skip tidyselftest + && ./x.py --stage 2 test + --host=aarch64-apple-darwin + --target=aarch64-apple-darwin + src/tools/cargo RUST_CONFIGURE_ARGS: >- --enable-sanitizers --enable-profiler @@ -606,12 +636,43 @@ auto: # previous attempts have timed out multiple times. Remove/revert this job if # this hangs or times out, or if it becomes the slowest Merge CI job, and let # T-infra know. - - name: aarch64-apple-macos-26 + - name: aarch64-apple-macos-26-1 doc_url: https://github.com/rust-lang/rust/issues/157687 env: - SCRIPT: > - ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin && - ./x.py --stage 2 test --host=aarch64-apple-darwin --target=aarch64-apple-darwin src/tools/cargo + # NOTE: keep in sync with `src/ci/docker/scripts/stage_2_test_set1.sh` + SCRIPT: >- + ./x.py --stage 2 test + --host=aarch64-apple-darwin + --target=aarch64-apple-darwin + --skip compiler + --skip src + RUST_CONFIGURE_ARGS: >- + --enable-sanitizers + --enable-profiler + --set rust.jemalloc + DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer + # Aarch64 tooling only needs to support macOS 11.0 and up as nothing else + # supports the hardware, so only need to test it there. + MACOSX_DEPLOYMENT_TARGET: 11.0 + MACOSX_STD_DEPLOYMENT_TARGET: 11.0 + <<: *job-macos-26 + + - name: aarch64-apple-macos-26-2 + doc_url: https://github.com/rust-lang/rust/issues/157687 + env: + # NOTE: keep in sync with `src/ci/docker/scripts/stage_2_test_set2.sh`, + # union `src/tools/cargo` specifically. + SCRIPT: >- + ./x.py --stage 2 test + --host=aarch64-apple-darwin + --target=aarch64-apple-darwin + --skip tests + --skip library + --skip tidyselftest + && ./x.py --stage 2 test + --host=aarch64-apple-darwin + --target=aarch64-apple-darwin + src/tools/cargo RUST_CONFIGURE_ARGS: >- --enable-sanitizers --enable-profiler From e90ed380830b2d14f81111f79d736dc6eef78b8b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 4 Aug 2026 11:54:07 +1000 Subject: [PATCH 16/42] Remove unused args from `ConstAnalysis` methods --- .../src/dataflow_const_prop.rs | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index 7f2e5c05eb5d3..f57245c0756b5 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -204,16 +204,10 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { } } - fn handle_operand( - &self, - operand: &Operand<'tcx>, - state: &mut State>, - ) -> ValueOrPlace> { + fn handle_operand(&self, operand: &Operand<'tcx>) -> ValueOrPlace> { match operand { Operand::RuntimeChecks(_) => ValueOrPlace::TOP, - Operand::Constant(constant) => { - ValueOrPlace::Value(self.handle_constant(constant, state)) - } + Operand::Constant(constant) => ValueOrPlace::Value(self.handle_constant(constant)), Operand::Copy(place) | Operand::Move(place) => { // On move, we would ideally flood the place with bottom. But with the current // framework this is not possible (similar to `InterpCx::eval_operand`). @@ -376,7 +370,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { operand, _, ) => { - let pointer = self.handle_operand(operand, state); + let pointer = self.handle_operand(operand); state.assign(target.as_ref(), pointer, &self.map); if let Some(target_len) = self.map.find_len(target.as_ref()) @@ -461,7 +455,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { } } Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), &self.map), - Rvalue::Use(operand, _) => return self.handle_operand(operand, state), + Rvalue::Use(operand, _) => return self.handle_operand(operand), Rvalue::CopyForDeref(_) => bug!("`CopyForDeref` in runtime MIR"), Rvalue::Ref(..) | Rvalue::Reborrow(..) | Rvalue::RawPtr(..) => { // We don't track such places. @@ -480,11 +474,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { ValueOrPlace::Value(val) } - fn handle_constant( - &self, - constant: &ConstOperand<'tcx>, - _state: &mut State>, - ) -> FlatSet { + fn handle_constant(&self, constant: &ConstOperand<'tcx>) -> FlatSet { constant .const_ .try_eval_scalar(self.tcx, self.typing_env) @@ -497,7 +487,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { targets: &'mir SwitchTargets, state: &mut State>, ) -> TerminatorEdges<'mir, 'tcx> { - let value = match self.handle_operand(discr, state) { + let value = match self.handle_operand(discr) { ValueOrPlace::Value(value) => value, ValueOrPlace::Place(place) => state.get_idx(place, &self.map), }; @@ -676,7 +666,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { op: &Operand<'tcx>, state: &mut State>, ) -> FlatSet> { - let value = match self.handle_operand(op, state) { + let value = match self.handle_operand(op) { ValueOrPlace::Value(value) => value, ValueOrPlace::Place(place) => state.get_idx(place, &self.map), }; From 6377970b4c4aca2f022e4c359bd9b83cd3b4ff47 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 4 Aug 2026 11:36:40 +1000 Subject: [PATCH 17/42] Split `apply_primary_terminator_effect` This method currently does two things: it applies the effect, and also computes the edges. However: - Three of the four call sites don't use the edges. - Most analyses just return `terminator.edges()` unconditionally. This commit separates the edge computation into a new method, `get_terminator_edges()`. It defaults to `terminator.edges()`, which means that most analyses don't need to define it. And now edges are only obtained when they are needed (in `Forward::apply_effects_in_block`). --- compiler/rustc_borrowck/src/dataflow.rs | 21 +++----- .../src/check_consts/resolver.rs | 9 ++-- .../src/framework/direction.rs | 4 +- .../rustc_mir_dataflow/src/framework/mod.rs | 19 ++++++-- .../rustc_mir_dataflow/src/framework/tests.rs | 7 ++- .../src/impls/borrowed_locals.rs | 7 ++- .../src/impls/initialized.rs | 48 ++++++++++++------- .../rustc_mir_dataflow/src/impls/liveness.rs | 18 +++---- .../src/impls/storage_liveness.rs | 7 ++- .../src/dataflow_const_prop.rs | 34 ++++++++----- compiler/rustc_mir_transform/src/liveness.rs | 7 ++- 11 files changed, 103 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 5bf692eaa7205..5bfe5ee64f050 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -2,9 +2,7 @@ use std::fmt; use rustc_data_structures::fx::FxIndexMap; use rustc_index::bit_set::{DenseBitSet, MixedBitSet}; -use rustc_middle::mir::{ - self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges, -}; +use rustc_middle::mir::{self, BasicBlock, Body, CallReturnPlaces, Location, Place}; use rustc_middle::ty::{RegionVid, TyCtxt}; use rustc_mir_dataflow::fmt::DebugWithContext; use rustc_mir_dataflow::impls::{ @@ -76,19 +74,15 @@ impl<'a, 'tcx> Analysis<'tcx> for Borrowck<'a, 'tcx> { self.ever_inits.apply_early_terminator_effect(&mut state.ever_inits, term, loc); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - term: &'mir mir::Terminator<'tcx>, + term: &mir::Terminator<'tcx>, loc: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { self.borrows.apply_primary_terminator_effect(&mut state.borrows, term, loc); self.uninits.apply_primary_terminator_effect(&mut state.uninits, term, loc); self.ever_inits.apply_primary_terminator_effect(&mut state.ever_inits, term, loc); - - // This return value doesn't matter. It's only used by `iterate_to_fixpoint`, which this - // analysis doesn't use. - TerminatorEdges::None } fn apply_call_return_effect( @@ -598,12 +592,12 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> { self.kill_loans_out_of_scope_at_location(state, location); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + terminator: &mir::Terminator<'tcx>, _location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind { for op in operands { if let mir::InlineAsmOperand::Out { place: Some(place), .. } @@ -613,7 +607,6 @@ impl<'tcx> rustc_mir_dataflow::Analysis<'tcx> for Borrows<'_, 'tcx> { } } } - terminator.edges() } } diff --git a/compiler/rustc_const_eval/src/check_consts/resolver.rs b/compiler/rustc_const_eval/src/check_consts/resolver.rs index a230f797b56fd..29b6e26d950d5 100644 --- a/compiler/rustc_const_eval/src/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/check_consts/resolver.rs @@ -8,7 +8,7 @@ use std::marker::PhantomData; use rustc_index::bit_set::MixedBitSet; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{ - self, BasicBlock, CallReturnPlaces, Local, Location, Statement, StatementKind, TerminatorEdges, + self, BasicBlock, CallReturnPlaces, Local, Location, Statement, StatementKind, }; use rustc_mir_dataflow::fmt::DebugWithContext; use rustc_mir_dataflow::{Analysis, JoinSemiLattice}; @@ -351,14 +351,13 @@ where self.transfer_function(state).visit_statement(statement, location); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + terminator: &mir::Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { self.transfer_function(state).visit_terminator(terminator, location); - terminator.edges() } fn apply_call_return_effect( diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs index 68c8e03de8022..7b577c2b9df4c 100644 --- a/compiler/rustc_mir_dataflow/src/framework/direction.rs +++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs @@ -194,7 +194,9 @@ impl Direction for Forward { let terminator = block_data.terminator(); let location = Location { block, statement_index: block_data.statements.len() }; analysis.apply_early_terminator_effect(state, terminator, location); - let edges = analysis.apply_primary_terminator_effect(state, terminator, location); + // Edges are obtained *before* calling `apply_primary_terminator_effect`. + let edges = analysis.get_terminator_edges(state, terminator, location); + analysis.apply_primary_terminator_effect(state, terminator, location); let exit_state = state; match edges { diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs index 8f58846152747..b767ed6005346 100644 --- a/compiler/rustc_mir_dataflow/src/framework/mod.rs +++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs @@ -196,19 +196,30 @@ pub trait Analysis<'tcx> { ) { } + /// Gets the terminator edges. Used by forward analyses only. Called *before* + /// `apply_primary_terminator_effect` is applied; this might seem strange but in practice + /// `MaybeInitializedPlaces` needs that ordering and other analyses work with either ordering. + fn get_terminator_edges<'mir>( + &self, + _state: &Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, + _location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + terminator.edges() + } + /// Updates the current dataflow state with the effect of evaluating a terminator. /// /// The effect of a successful return from a `Call` terminator should **not** be accounted for /// in this function. That should go in `apply_call_return_effect`. For example, in the /// `InitializedPlaces` analyses, the return place for a function call is not marked as /// initialized here. - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, _state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + _terminator: &mir::Terminator<'tcx>, _location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { - terminator.edges() + ) { } /* Edge-specific effects */ diff --git a/compiler/rustc_mir_dataflow/src/framework/tests.rs b/compiler/rustc_mir_dataflow/src/framework/tests.rs index 86ea3a34ae0ea..ee6330bfe1c2c 100644 --- a/compiler/rustc_mir_dataflow/src/framework/tests.rs +++ b/compiler/rustc_mir_dataflow/src/framework/tests.rs @@ -197,15 +197,14 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> { assert!(state.insert(idx)); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + _terminator: &mir::Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { let idx = self.effect(Effect::Primary.at_index(location.statement_index)); assert!(state.insert(idx)); - terminator.edges() } } diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index 9ec68f5260c05..c5b69c563b2fe 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -41,14 +41,13 @@ impl<'tcx> Analysis<'tcx> for MaybeBorrowedLocals { Self::transfer_function(state).visit_statement(statement, location); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir Terminator<'tcx>, + terminator: &Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { Self::transfer_function(state).visit_terminator(terminator, location); - terminator.edges() } } diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index 543c833326021..1b2c58c7e514c 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -391,14 +391,15 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { } } - fn apply_primary_terminator_effect<'mir>( + fn get_terminator_edges<'mir>( &self, - state: &mut Self::Domain, + state: &Self::Domain, terminator: &'mir mir::Terminator<'tcx>, - location: Location, + _location: Location, ) -> TerminatorEdges<'mir, 'tcx> { - // Note: `edges` must be computed first because `drop_flag_effects_for_location` can change - // the result of `is_unwind_dead`. + // Note: this relies on `get_terminator_edges` being called before + // `apply_primary_terminator_effect` because the result of `is_unwind_dead` is affected by + // the `drop_flag_effects_for_location` in `apply_primary_terminator_effect`. let mut edges = terminator.edges(); if self.skip_unreachable_unwind && let mir::TerminatorKind::Drop { target, unwind, place, replace: _, drop: _ } = @@ -408,10 +409,18 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { { edges = TerminatorEdges::Single(target); } + edges + } + + fn apply_primary_terminator_effect( + &self, + state: &mut Self::Domain, + _terminator: &mir::Terminator<'tcx>, + location: Location, + ) { drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { Self::update_bits(state, path, s) }); - edges } fn apply_call_return_effect( @@ -514,15 +523,12 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { // mutable borrow occurs. Places cannot become uninitialized through a mutable reference. } - fn apply_primary_terminator_effect<'mir>( + fn get_terminator_edges<'mir>( &self, - state: &mut Self::Domain, + _state: &Self::Domain, terminator: &'mir mir::Terminator<'tcx>, location: Location, ) -> TerminatorEdges<'mir, 'tcx> { - drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { - Self::update_bits(state, path, s) - }); if self.skip_unreachable_unwind.contains(location.block) { let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() }; assert_matches!(unwind, mir::UnwindAction::Cleanup(_)); @@ -532,6 +538,17 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { } } + fn apply_primary_terminator_effect( + &self, + state: &mut Self::Domain, + _terminator: &mir::Terminator<'tcx>, + location: Location, + ) { + drop_flag_effects_for_location(self.body, self.move_data, location, |path, s| { + Self::update_bits(state, path, s) + }); + } + fn apply_call_return_effect( &self, state: &mut Self::Domain, @@ -633,13 +650,13 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { } } - #[instrument(skip(self, state, terminator), level = "debug")] - fn apply_primary_terminator_effect<'mir>( + #[instrument(skip(self, state, _terminator), level = "debug")] + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + _terminator: &mir::Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { let move_data = self.move_data(); let init_loc_map = &move_data.init_loc_map; @@ -652,7 +669,6 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { None } })); - terminator.edges() } fn apply_call_return_effect( diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index b690e86b747d5..da2ea948366db 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -1,8 +1,6 @@ use rustc_index::bit_set::DenseBitSet; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; -use rustc_middle::mir::{ - self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges, -}; +use rustc_middle::mir::{self, CallReturnPlaces, Local, Location, Place, StatementKind}; use crate::{Analysis, Backward, GenKill}; @@ -55,14 +53,13 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { TransferFunction(state).visit_statement(statement, location); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + terminator: &mir::Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { TransferFunction(state).visit_terminator(terminator, location); - terminator.edges() } fn apply_call_return_effect( @@ -301,14 +298,13 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { TransferFunction(state).visit_statement(statement, location); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'mir mir::Terminator<'tcx>, + terminator: &mir::Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { TransferFunction(state).visit_terminator(terminator, location); - terminator.edges() } fn apply_call_return_effect( diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs index 494fb4098cfc1..558bf0a5603fa 100644 --- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs @@ -295,12 +295,12 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage { } } - fn apply_primary_terminator_effect<'t>( + fn apply_primary_terminator_effect( &self, state: &mut Self::Domain, - terminator: &'t Terminator<'tcx>, + terminator: &Terminator<'tcx>, loc: Location, - ) -> TerminatorEdges<'t, 'tcx> { + ) { match terminator.kind { // For call terminators the destination requires storage for the call // and after the call returns successfully, but not after a panic. @@ -333,7 +333,6 @@ impl<'tcx> Analysis<'tcx> for MaybeRequiresStorage { } self.check_for_move(state, loc); - terminator.edges() } fn apply_call_return_effect( diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index f57245c0756b5..4e00bf1bf6559 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -122,19 +122,34 @@ impl<'tcx> Analysis<'tcx> for ConstAnalysis<'_, 'tcx> { } } - fn apply_primary_terminator_effect<'mir>( + fn get_terminator_edges<'mir>( &self, - state: &mut Self::Domain, + state: &Self::Domain, terminator: &'mir Terminator<'tcx>, _location: Location, ) -> TerminatorEdges<'mir, 'tcx> { if state.is_reachable() { - self.handle_terminator(terminator, state) + if let TerminatorKind::SwitchInt { discr, targets } = &terminator.kind { + self.get_switch_int_edges(discr, targets, state) + } else { + terminator.edges() + } } else { TerminatorEdges::None } } + fn apply_primary_terminator_effect( + &self, + state: &mut Self::Domain, + terminator: &Terminator<'tcx>, + _location: Location, + ) { + if state.is_reachable() { + self.handle_terminator(terminator, state) + } + } + fn apply_call_return_effect( &self, state: &mut Self::Domain, @@ -222,7 +237,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { &self, terminator: &'mir Terminator<'tcx>, state: &mut State>, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { match &terminator.kind { TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => { // Effect is applied by `handle_call_return`. @@ -234,14 +249,12 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { // They would have an effect, but are not allowed in this phase. bug!("encountered disallowed terminator"); } - TerminatorKind::SwitchInt { discr, targets } => { - return self.handle_switch_int(discr, targets, state); - } TerminatorKind::TailCall { .. } => { // FIXME(explicit_tail_calls): determine if we need to do something here (probably // not) } - TerminatorKind::Goto { .. } + TerminatorKind::SwitchInt { .. } + | TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return @@ -253,7 +266,6 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { // These terminators have no effect on the analysis. } } - terminator.edges() } fn handle_call_return( @@ -481,11 +493,11 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { .map_or(FlatSet::Top, FlatSet::Elem) } - fn handle_switch_int<'mir>( + fn get_switch_int_edges<'mir>( &self, discr: &'mir Operand<'tcx>, targets: &'mir SwitchTargets, - state: &mut State>, + state: &State>, ) -> TerminatorEdges<'mir, 'tcx> { let value = match self.handle_operand(discr) { ValueOrPlace::Value(value) => value, diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index 32951ea0162a6..c895819a9f8cc 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -1342,14 +1342,13 @@ impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> { self.transfer_function(trans).visit_statement(statement, location); } - fn apply_primary_terminator_effect<'mir>( + fn apply_primary_terminator_effect( &self, trans: &mut Self::Domain, - terminator: &'mir Terminator<'tcx>, + terminator: &Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { + ) { self.transfer_function(trans).visit_terminator(terminator, location); - terminator.edges() } fn apply_call_return_effect( From 099db74dec7742ceb71cd1858f1f3a6e495e9418 Mon Sep 17 00:00:00 2001 From: Peter Szilvasi Date: Wed, 5 Aug 2026 06:22:34 +0000 Subject: [PATCH 18/42] Update error message in documentation comments --- library/core/src/fmt/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index e5d3ccb027b70..a5896f3f863cf 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -1611,7 +1611,7 @@ pub trait UpperExp: PointeeSized { /// /// let mut output = String::new(); /// fmt::write(&mut output, format_args!("Hello {}!", "world")) -/// .expect("Error occurred while trying to write in String"); +/// .expect("Writing to a `String` should not fail"); /// assert_eq!(output, "Hello world!"); /// ``` /// @@ -1622,7 +1622,7 @@ pub trait UpperExp: PointeeSized { /// /// let mut output = String::new(); /// write!(&mut output, "Hello {}!", "world") -/// .expect("Error occurred while trying to write in String"); +/// .expect("Writing to a `String` should not fail"); /// assert_eq!(output, "Hello world!"); /// ``` /// From e6b1e68205312ff993fe5232f154cd761cf01f59 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 18 Jul 2026 17:56:34 +0100 Subject: [PATCH 19/42] Cap socket send length to c_int::MAX on Apple targets On Apple, `send`/`sendto` reject a length larger than `c_int::MAX` with `EINVAL` instead of doing a short send. The send length was only clamped to `wrlen_t::MAX` (a no-op on 64-bit unix), so writing more than `c_int::MAX` bytes to a socket failed on macOS. Add a `MAX_SEND_LEN` cap (`c_int::MAX` on Apple, `wrlen_t::MAX` elsewhere), used in `write`, `send`, `send_to`, and `send_with_flags`. --- library/std/src/net/tcp/tests.rs | 31 +++++++++++++++++++ .../std/src/sys/net/connection/socket/mod.rs | 9 ++++-- .../src/sys/net/connection/socket/tests.rs | 12 +++++++ .../std/src/sys/net/connection/socket/unix.rs | 2 +- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index cada78a0d55ad..8422deaa0335b 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -956,3 +956,34 @@ fn connect_timeout_valid() { let addr = listener.local_addr().unwrap(); TcpStream::connect_timeout(&addr, Duration::from_secs(2)).unwrap(); } + +// #115325: writing a buffer larger than `c_int::MAX` bytes used to fail on +// macOS with `EINVAL`; `write_all` should now transfer it via short sends. +#[test] +#[cfg(target_pointer_width = "64")] +#[ignore = "requires ~2 GiB of memory"] +fn write_buffer_larger_than_c_int_max() { + const LEN: usize = crate::ffi::c_int::MAX as usize + 1; + + let listener = t!(TcpListener::bind("127.0.0.1:0")); + let addr = t!(listener.local_addr()); + let reader = thread::spawn(move || { + let (mut sock, _) = t!(listener.accept()); + let mut received = 0usize; + let mut buf = vec![0u8; 1 << 20]; + loop { + match sock.read(&mut buf) { + Ok(0) => break, + Ok(n) => received += n, + Err(e) => panic!("read error: {e}"), + } + } + received + }); + + let mut stream = t!(TcpStream::connect(addr)); + let data = vec![0u8; LEN]; + t!(stream.write_all(&data)); + drop(stream); // signal EOF so the reader loop terminates + assert_eq!(reader.join().unwrap(), LEN); +} diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 66aa2a804db22..985cc94e41330 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -35,6 +35,9 @@ cfg_select! { use netc as c; +const MAX_SEND_LEN: usize = + if cfg!(target_vendor = "apple") { c_int::MAX as usize } else { ::MAX as usize }; + cfg_select! { any( target_os = "dragonfly", @@ -430,7 +433,7 @@ impl TcpStream { } pub fn write(&self, buf: &[u8]) -> io::Result { - let len = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; + let len = cmp::min(buf.len(), MAX_SEND_LEN) as wrlen_t; let ret = cvt(unsafe { c::send(self.inner.as_raw(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL) })?; @@ -707,7 +710,7 @@ impl UdpSocket { } pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result { - let len = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; + let len = cmp::min(buf.len(), MAX_SEND_LEN) as wrlen_t; let (dst, dstlen) = socket_addr_to_c(dst); let ret = cvt(unsafe { c::sendto( @@ -860,7 +863,7 @@ impl UdpSocket { } pub fn send(&self, buf: &[u8]) -> io::Result { - let len = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; + let len = cmp::min(buf.len(), MAX_SEND_LEN) as wrlen_t; let ret = cvt(unsafe { c::send(self.inner.as_raw(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL) })?; diff --git a/library/std/src/sys/net/connection/socket/tests.rs b/library/std/src/sys/net/connection/socket/tests.rs index 049355afca7ac..5aff1f3770f37 100644 --- a/library/std/src/sys/net/connection/socket/tests.rs +++ b/library/std/src/sys/net/connection/socket/tests.rs @@ -17,3 +17,15 @@ fn no_lookup_host_duplicates() { "There should be no duplicate localhost entries" ); } + +// #115325: on Apple, `send` rejects a length > `c_int::MAX` with `EINVAL`, so +// the clamp must not regress to the unbounded `wrlen_t::MAX`. +#[test] +fn max_send_len_within_platform_limit() { + if cfg!(target_vendor = "apple") { + assert_eq!(MAX_SEND_LEN, c_int::MAX as usize); + } else { + assert_eq!(MAX_SEND_LEN, ::MAX as usize); + } + assert_eq!(crate::cmp::min(MAX_SEND_LEN.saturating_add(1), MAX_SEND_LEN), MAX_SEND_LEN); +} diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index 41850574c96fa..c687ed652d74a 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -279,7 +279,7 @@ impl Socket { #[cfg(not(target_os = "wasi"))] pub fn send_with_flags(&self, buf: &[u8], flags: c_int) -> io::Result { - let len = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; + let len = cmp::min(buf.len(), super::MAX_SEND_LEN) as wrlen_t; let ret = cvt(unsafe { libc::send(self.as_raw_fd(), buf.as_ptr() as *const c_void, len, flags) })?; From cefe30fe337a1d3c0e53d091d012ac628de09a41 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 18 Jul 2026 23:01:27 +0100 Subject: [PATCH 20/42] Return EMSGSIZE for oversized datagram sends --- .../std/src/sys/net/connection/socket/mod.rs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 985cc94e41330..769dc66af8ed1 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -709,14 +709,18 @@ impl UdpSocket { self.inner.peek_from(buf) } + // `MAX_SEND_LEN` is `usize::MAX` off Apple/Windows, where the guard is a no-op. + #[allow(clippy::absurd_extreme_comparisons)] pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result { - let len = cmp::min(buf.len(), MAX_SEND_LEN) as wrlen_t; + if buf.len() > MAX_SEND_LEN { + return Err(io::Error::from_raw_os_error(c::EMSGSIZE)); + } let (dst, dstlen) = socket_addr_to_c(dst); let ret = cvt(unsafe { c::sendto( self.inner.as_raw(), buf.as_ptr() as *const c_void, - len, + buf.len() as wrlen_t, MSG_NOSIGNAL, dst.as_ptr(), dstlen, @@ -862,10 +866,19 @@ impl UdpSocket { self.inner.peek(buf) } + // `MAX_SEND_LEN` is `usize::MAX` off Apple/Windows, where the guard is a no-op. + #[allow(clippy::absurd_extreme_comparisons)] pub fn send(&self, buf: &[u8]) -> io::Result { - let len = cmp::min(buf.len(), MAX_SEND_LEN) as wrlen_t; + if buf.len() > MAX_SEND_LEN { + return Err(io::Error::from_raw_os_error(c::EMSGSIZE)); + } let ret = cvt(unsafe { - c::send(self.inner.as_raw(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL) + c::send( + self.inner.as_raw(), + buf.as_ptr() as *const c_void, + buf.len() as wrlen_t, + MSG_NOSIGNAL, + ) })?; Ok(ret as usize) } From 45072115b51d774ade2ce577abc551a98d5c4dd1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 18 Jul 2026 23:01:38 +0100 Subject: [PATCH 21/42] Expose EMSGSIZE in the Windows netc shim --- library/std/src/sys/net/connection/socket/windows.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/net/connection/socket/windows.rs b/library/std/src/sys/net/connection/socket/windows.rs index aa6b6756357ac..075e77bc4457c 100644 --- a/library/std/src/sys/net/connection/socket/windows.rs +++ b/library/std/src/sys/net/connection/socket/windows.rs @@ -31,8 +31,8 @@ pub(super) mod netc { IP_DROP_MEMBERSHIP, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_TTL, IPPROTO_IP, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, IPV6_DROP_MEMBERSHIP, IPV6_MULTICAST_LOOP, IPV6_V6ONLY, SO_BROADCAST, SO_RCVTIMEO, SO_SNDTIMEO, SOCK_DGRAM, SOCK_STREAM, SOCKADDR as sockaddr, - SOCKADDR_STORAGE as sockaddr_storage, SOL_SOCKET, bind, connect, freeaddrinfo, getpeername, - getsockname, getsockopt, listen, setsockopt, + SOCKADDR_STORAGE as sockaddr_storage, SOL_SOCKET, WSAEMSGSIZE as EMSGSIZE, bind, connect, + freeaddrinfo, getpeername, getsockname, getsockopt, listen, setsockopt, }; #[allow(non_camel_case_types)] From 28e1c851addbd55e44c708df65e9b713f761f0a1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 18 Jul 2026 23:01:38 +0100 Subject: [PATCH 22/42] Add test for oversized datagram sends --- library/std/src/net/udp/tests.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/library/std/src/net/udp/tests.rs b/library/std/src/net/udp/tests.rs index eeb6afdb072eb..e1e41837bf392 100644 --- a/library/std/src/net/udp/tests.rs +++ b/library/std/src/net/udp/tests.rs @@ -374,3 +374,17 @@ fn set_nonblocking() { } }) } + +// #115325: a datagram larger than `c_int::MAX` bytes can't be sent atomically +// and must be rejected rather than truncated. +#[test] +#[cfg(target_pointer_width = "64")] +#[ignore = "requires ~2 GiB of memory"] +fn send_datagram_larger_than_c_int_max() { + let socket = t!(UdpSocket::bind("127.0.0.1:0")); + let addr = t!(socket.local_addr()); + let data = vec![0u8; crate::ffi::c_int::MAX as usize + 1]; + assert!(socket.send_to(&data, addr).is_err()); + t!(socket.connect(addr)); + assert!(socket.send(&data).is_err()); +} From 6477fb587109a46d3b4994260e30d881e037f858 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 25 Jul 2026 17:16:08 +0100 Subject: [PATCH 23/42] address feedbacks --- library/std/src/net/tcp/tests.rs | 27 ++++++++++++++++--- .../src/sys/net/connection/socket/tests.rs | 1 - 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index 8422deaa0335b..4fc4b4aa80e9c 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -960,11 +960,27 @@ fn connect_timeout_valid() { // #115325: writing a buffer larger than `c_int::MAX` bytes used to fail on // macOS with `EINVAL`; `write_all` should now transfer it via short sends. #[test] -#[cfg(target_pointer_width = "64")] -#[ignore = "requires ~2 GiB of memory"] +#[cfg(all(target_pointer_width = "64", unix))] fn write_buffer_larger_than_c_int_max() { const LEN: usize = crate::ffi::c_int::MAX as usize + 1; + // Back the source buffer with a read-only anonymous mmap rather than a 2 GiB + // `Vec`. The pages are demand-zero and never written, so they stay mapped to + // the shared zero page and the test doesn't actually consume ~2 GiB of + // physical memory while `write_all` reads through the buffer. + let data = unsafe { + let ptr = libc::mmap( + crate::ptr::null_mut(), + LEN, + libc::PROT_READ, + libc::MAP_PRIVATE | libc::MAP_ANON, + -1, + 0, + ); + assert_ne!(ptr, libc::MAP_FAILED, "mmap failed: {}", crate::io::Error::last_os_error()); + crate::slice::from_raw_parts(ptr as *const u8, LEN) + }; + let listener = t!(TcpListener::bind("127.0.0.1:0")); let addr = t!(listener.local_addr()); let reader = thread::spawn(move || { @@ -982,8 +998,11 @@ fn write_buffer_larger_than_c_int_max() { }); let mut stream = t!(TcpStream::connect(addr)); - let data = vec![0u8; LEN]; - t!(stream.write_all(&data)); + t!(stream.write_all(data)); drop(stream); // signal EOF so the reader loop terminates assert_eq!(reader.join().unwrap(), LEN); + + unsafe { + assert_eq!(libc::munmap(data.as_ptr() as *mut libc::c_void, LEN), 0); + } } diff --git a/library/std/src/sys/net/connection/socket/tests.rs b/library/std/src/sys/net/connection/socket/tests.rs index 5aff1f3770f37..e6f02d7a93859 100644 --- a/library/std/src/sys/net/connection/socket/tests.rs +++ b/library/std/src/sys/net/connection/socket/tests.rs @@ -27,5 +27,4 @@ fn max_send_len_within_platform_limit() { } else { assert_eq!(MAX_SEND_LEN, ::MAX as usize); } - assert_eq!(crate::cmp::min(MAX_SEND_LEN.saturating_add(1), MAX_SEND_LEN), MAX_SEND_LEN); } From 08f76ddaad583eb79e96410b17d16dc8c78f9041 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 5 Aug 2026 10:37:21 +0100 Subject: [PATCH 24/42] Share the zeroed-mmap test buffer between the TCP and UDP tests The pages are demand-zero and never written, so the datagram test no longer needs ~2 GiB of memory and its `#[ignore]` can go away. Keep a `Vec`-backed copy for non-unix targets, where `mmap` isn't available. --- library/std/src/net/tcp/tests.rs | 26 ++++------------- library/std/src/net/tests.rs | 50 ++++++++++++++++++++++++++++++++ library/std/src/net/udp/tests.rs | 22 ++++++++++++-- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index 4fc4b4aa80e9c..45a512cb9b4c7 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -964,22 +964,10 @@ fn connect_timeout_valid() { fn write_buffer_larger_than_c_int_max() { const LEN: usize = crate::ffi::c_int::MAX as usize + 1; - // Back the source buffer with a read-only anonymous mmap rather than a 2 GiB - // `Vec`. The pages are demand-zero and never written, so they stay mapped to - // the shared zero page and the test doesn't actually consume ~2 GiB of - // physical memory while `write_all` reads through the buffer. - let data = unsafe { - let ptr = libc::mmap( - crate::ptr::null_mut(), - LEN, - libc::PROT_READ, - libc::MAP_PRIVATE | libc::MAP_ANON, - -1, - 0, - ); - assert_ne!(ptr, libc::MAP_FAILED, "mmap failed: {}", crate::io::Error::last_os_error()); - crate::slice::from_raw_parts(ptr as *const u8, LEN) - }; + // Back the source buffer with a read-only anonymous mapping rather than a + // 2 GiB `Vec`, so the test doesn't actually consume ~2 GiB of physical + // memory while `write_all` reads through the buffer. + let data = crate::net::tests::ZeroedMmap::new(LEN); let listener = t!(TcpListener::bind("127.0.0.1:0")); let addr = t!(listener.local_addr()); @@ -998,11 +986,7 @@ fn write_buffer_larger_than_c_int_max() { }); let mut stream = t!(TcpStream::connect(addr)); - t!(stream.write_all(data)); + t!(stream.write_all(&data)); drop(stream); // signal EOF so the reader loop terminates assert_eq!(reader.join().unwrap(), LEN); - - unsafe { - assert_eq!(libc::munmap(data.as_ptr() as *mut libc::c_void, LEN), 0); - } } diff --git a/library/std/src/net/tests.rs b/library/std/src/net/tests.rs index cb1c1ca36b124..213c9a5a3e3a1 100644 --- a/library/std/src/net/tests.rs +++ b/library/std/src/net/tests.rs @@ -37,6 +37,56 @@ pub fn compare_ignore_zoneid(a: &SocketAddr, b: &SocketAddr) -> bool { } } +/// A read-only anonymous mapping of `len` zero bytes. +/// +/// The tests that need a buffer larger than `c_int::MAX` use this instead of a +/// `Vec`: the pages are demand-zero and never written, so they stay mapped to +/// the shared zero page and the mapping doesn't actually consume `len` bytes of +/// physical memory. +#[cfg(all(target_pointer_width = "64", unix))] +pub struct ZeroedMmap { + ptr: *mut libc::c_void, + len: usize, +} + +#[cfg(all(target_pointer_width = "64", unix))] +impl ZeroedMmap { + pub fn new(len: usize) -> ZeroedMmap { + let ptr = unsafe { + libc::mmap( + crate::ptr::null_mut(), + len, + libc::PROT_READ, + libc::MAP_PRIVATE | libc::MAP_ANON, + -1, + 0, + ) + }; + assert_ne!(ptr, libc::MAP_FAILED, "mmap failed: {}", crate::io::Error::last_os_error()); + ZeroedMmap { ptr, len } + } +} + +#[cfg(all(target_pointer_width = "64", unix))] +impl crate::ops::Deref for ZeroedMmap { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + // SAFETY: the mapping is live for `self.len` readable bytes until `Drop`. + unsafe { crate::slice::from_raw_parts(self.ptr as *const u8, self.len) } + } +} + +#[cfg(all(target_pointer_width = "64", unix))] +impl Drop for ZeroedMmap { + fn drop(&mut self) { + // SAFETY: `ptr`/`len` come from the `mmap` call above and are unmapped once. + unsafe { + libc::munmap(self.ptr, self.len); + } + } +} + #[test] fn hostname_smoketest() { // Just a smoke test to ensure it can be called. diff --git a/library/std/src/net/udp/tests.rs b/library/std/src/net/udp/tests.rs index e1e41837bf392..38d6dab80f64e 100644 --- a/library/std/src/net/udp/tests.rs +++ b/library/std/src/net/udp/tests.rs @@ -378,12 +378,30 @@ fn set_nonblocking() { // #115325: a datagram larger than `c_int::MAX` bytes can't be sent atomically // and must be rejected rather than truncated. #[test] -#[cfg(target_pointer_width = "64")] -#[ignore = "requires ~2 GiB of memory"] +#[cfg(all(target_pointer_width = "64", unix))] fn send_datagram_larger_than_c_int_max() { + // A read-only anonymous mapping rather than a 2 GiB `Vec`: the datagram is + // rejected before the kernel ever reads the pages, so this costs no + // physical memory. + let data = crate::net::tests::ZeroedMmap::new(crate::ffi::c_int::MAX as usize + 1); + let socket = t!(UdpSocket::bind("127.0.0.1:0")); let addr = t!(socket.local_addr()); + assert!(socket.send_to(&data, addr).is_err()); + t!(socket.connect(addr)); + assert!(socket.send(&data).is_err()); +} + +// Same as above, for the platforms where the `mmap` trick isn't available and +// the buffer really has to be allocated. +#[test] +#[cfg(all(target_pointer_width = "64", not(unix)))] +#[ignore = "requires ~2 GiB of memory"] +fn send_datagram_larger_than_c_int_max() { let data = vec![0u8; crate::ffi::c_int::MAX as usize + 1]; + + let socket = t!(UdpSocket::bind("127.0.0.1:0")); + let addr = t!(socket.local_addr()); assert!(socket.send_to(&data, addr).is_err()); t!(socket.connect(addr)); assert!(socket.send(&data).is_err()); From 0ae62fba95f9ed341a1351053f7f0926a84c0f6f Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:16:51 +0330 Subject: [PATCH 25/42] Add regression test for array type recovery in generic arguments --- .../recover/array-type-no-semi-turbofish-81097.rs | 6 ++++++ .../array-type-no-semi-turbofish-81097.stderr | 14 ++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/ui/parser/recover/array-type-no-semi-turbofish-81097.rs create mode 100644 tests/ui/parser/recover/array-type-no-semi-turbofish-81097.stderr diff --git a/tests/ui/parser/recover/array-type-no-semi-turbofish-81097.rs b/tests/ui/parser/recover/array-type-no-semi-turbofish-81097.rs new file mode 100644 index 0000000000000..e0088837fac8e --- /dev/null +++ b/tests/ui/parser/recover/array-type-no-semi-turbofish-81097.rs @@ -0,0 +1,6 @@ +//! Regression test for . + +fn main() { + drop::<[(), 0]>([]); + //~^ ERROR expected `;` or `]`, found `,` +} diff --git a/tests/ui/parser/recover/array-type-no-semi-turbofish-81097.stderr b/tests/ui/parser/recover/array-type-no-semi-turbofish-81097.stderr new file mode 100644 index 0000000000000..17dc812c8e6ec --- /dev/null +++ b/tests/ui/parser/recover/array-type-no-semi-turbofish-81097.stderr @@ -0,0 +1,14 @@ +error: expected `;` or `]`, found `,` + --> $DIR/array-type-no-semi-turbofish-81097.rs:4:15 + | +LL | drop::<[(), 0]>([]); + | ^ expected `;` or `]` + | +help: you might have meant to use `;` as the separator + | +LL - drop::<[(), 0]>([]); +LL + drop::<[(); 0]>([]); + | + +error: aborting due to 1 previous error + From 0aa333d403918a35d62ab82c5793f9e879f5b9dc Mon Sep 17 00:00:00 2001 From: Evgenii Zheltonozhskii Date: Wed, 5 Aug 2026 15:36:43 +0300 Subject: [PATCH 26/42] Add tests for new solver issues --- .../alias-liveness/gat-static-unnormalized.rs | 47 +++++++++++++++++++ .../generalize/eagerly-normalizing-aliases.rs | 31 ++++++++++++ .../recursive-hidden-type-canonicalization.rs | 28 +++++++++++ ...ursive-hidden-type-canonicalization.stderr | 14 ++++++ .../next-solver/opaques/stalled-goal-rerun.rs | 33 +++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 tests/ui/borrowck/alias-liveness/gat-static-unnormalized.rs create mode 100644 tests/ui/traits/next-solver/generalize/eagerly-normalizing-aliases.rs create mode 100644 tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.rs create mode 100644 tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr create mode 100644 tests/ui/traits/next-solver/opaques/stalled-goal-rerun.rs diff --git a/tests/ui/borrowck/alias-liveness/gat-static-unnormalized.rs b/tests/ui/borrowck/alias-liveness/gat-static-unnormalized.rs new file mode 100644 index 0000000000000..bb8dfc4553146 --- /dev/null +++ b/tests/ui/borrowck/alias-liveness/gat-static-unnormalized.rs @@ -0,0 +1,47 @@ +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass + +// Regression test for #158461. Outlives clauses from the parameter environment +// need to be normalized before alias liveness analysis can match them. + +trait Id { + type SelfType; +} + +impl Id for T { + type SelfType = T; +} + +trait Foo { + type Assoc<'a> + where + Self: 'a; + + fn assoc(&mut self) -> Self::Assoc<'_>; +} + +// The normalized `'static` bound allows this value's borrow to end immediately. +fn overlapping_mut(mut t: T) +where + T: Foo, + for<'a> as Id>::SelfType: 'static, +{ + let a = t.assoc(); + let b = t.assoc(); +} + +// This is a distinct liveness path: the owner can be moved while the projected +// value remains live. +fn live_past_borrow(mut t: T) +where + T: Foo, + for<'a> as Id>::SelfType: 'static, +{ + let x = t.assoc(); + drop(t); + drop(x); +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/generalize/eagerly-normalizing-aliases.rs b/tests/ui/traits/next-solver/generalize/eagerly-normalizing-aliases.rs new file mode 100644 index 0000000000000..2d5ae9d21010f --- /dev/null +++ b/tests/ui/traits/next-solver/generalize/eagerly-normalizing-aliases.rs @@ -0,0 +1,31 @@ +//@ revisions: old next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +//@ check-pass + +// Regression test for trait-system-refactor-initiative#262. + +trait View {} + +trait HasAssoc { + type Assoc; +} + +struct StableVec(T); + +impl View for StableVec {} + +fn assert_view(f: F) -> F { + f +} + +fn store() -> StableVec +where + T: HasAssoc, + StableVec: View, +{ + let x = todo!(); + assert_view(x) +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.rs b/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.rs new file mode 100644 index 0000000000000..f93410550bdcf --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.rs @@ -0,0 +1,28 @@ +//@ compile-flags: -Znext-solver + +// Regression test for trait-system-refactor-initiative#267. This recursively +// changing opaque type used to overflow the stack while instantiating a +// canonical response. + +trait Distribution {} + +impl Distribution<(A, B)> for u32 +where + u32: Distribution, + u32: Distribution, +{ +} + +fn require_distribution, T>(_: *mut T) {} + +fn random_paulis() -> Option<*mut impl Sized> { + if false { + let r = random_paulis().unwrap(); + //~^ ERROR type annotations needed + require_distribution::(r); + } + + None +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr b/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr new file mode 100644 index 0000000000000..b3b173def6a01 --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/recursive-hidden-type-canonicalization.stderr @@ -0,0 +1,14 @@ +error[E0282]: type annotations needed for `*mut _` + --> $DIR/recursive-hidden-type-canonicalization.rs:20:13 + | +LL | let r = random_paulis().unwrap(); + | ^ + | +help: consider giving `r` an explicit type, where the placeholder `_` is specified + | +LL | let r: *mut _ = random_paulis().unwrap(); + | ++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0282`. diff --git a/tests/ui/traits/next-solver/opaques/stalled-goal-rerun.rs b/tests/ui/traits/next-solver/opaques/stalled-goal-rerun.rs new file mode 100644 index 0000000000000..8402d695749cd --- /dev/null +++ b/tests/ui/traits/next-solver/opaques/stalled-goal-rerun.rs @@ -0,0 +1,33 @@ +//@ compile-flags: -Znext-solver +//@ check-pass + +// Regression test for trait-system-refactor-initiative#267. This used to hang +// because a fast-path goal was not rerun after the opaque type storage changed. + +trait Distribution {} + +impl Distribution<()> for u32 {} + +impl Distribution<(A, B)> for u32 +where + u32: Distribution, + u32: Distribution, +{ +} + +trait Trait { + type Item; +} + +impl Trait for Option +where + u32: Distribution, +{ + type Item = T; +} + +fn random_paulis() -> impl Trait { + None +} + +fn main() {} From 7eac635e0a11a04fb08ccc24676a874db3c29b52 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 5 Aug 2026 15:32:21 +0200 Subject: [PATCH 27/42] fix: use fully qualified paths in `walk_visitable_list!` .. so that you don't need to import `TypeVisitable` in order to use it. I used the `TypeVisitable` from `rustc_type_ir` and not `rustc_middle::ty` because the macro is called inside `rustc_type_ir` itself. --- compiler/rustc_ast_ir/src/visit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast_ir/src/visit.rs b/compiler/rustc_ast_ir/src/visit.rs index 8315c080dfa86..1a60688312473 100644 --- a/compiler/rustc_ast_ir/src/visit.rs +++ b/compiler/rustc_ast_ir/src/visit.rs @@ -99,7 +99,7 @@ macro_rules! walk_list { macro_rules! walk_visitable_list { ($visitor: expr, $list: expr $(, $($extra_args: expr),* )?) => { for elem in $list { - $crate::try_visit!(elem.visit_with($visitor $(, $($extra_args,)* )?)); + $crate::try_visit!(::rustc_type_ir::TypeVisitable::visit_with(elem, $visitor $(, $($extra_args,)* )?)); } } } From fee8922aa3defede027ff7d04373f4cb7e80b9bc Mon Sep 17 00:00:00 2001 From: Moulins Date: Sun, 2 Aug 2026 18:40:51 +0200 Subject: [PATCH 28/42] rustc_abi: Add `LayoutData::is_variant_uninhabited` method This is a cheaper alternative to `TyAndLayout::for_variant(_, idx).is_uninhabited()` --- compiler/rustc_abi/src/lib.rs | 11 +++++++++++ compiler/rustc_codegen_cranelift/src/discriminant.rs | 2 +- compiler/rustc_codegen_ssa/src/mir/place.rs | 2 +- .../rustc_const_eval/src/interpret/discriminant.rs | 4 ++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index e0e9ecaa49c63..1e0fd78b4dd75 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -2203,6 +2203,17 @@ impl LayoutData { pub fn is_uninhabited(&self) -> bool { self.uninhabited } + + /// Returns `true` if the given variant is uninhabited. + pub fn is_variant_uninhabited(&self, variant: VariantIdx) -> bool { + match self.variants { + Variants::Empty => true, + Variants::Single { index } => variant != index || self.uninhabited, + Variants::Multiple { ref variants, .. } => { + variants.get(variant).map(|v| v.uninhabited).unwrap_or(true) + } + } + } } impl fmt::Debug for LayoutData diff --git a/compiler/rustc_codegen_cranelift/src/discriminant.rs b/compiler/rustc_codegen_cranelift/src/discriminant.rs index 8818e8634952e..fd4f1d8c61e55 100644 --- a/compiler/rustc_codegen_cranelift/src/discriminant.rs +++ b/compiler/rustc_codegen_cranelift/src/discriminant.rs @@ -14,7 +14,7 @@ pub(crate) fn codegen_set_discriminant<'tcx>( variant_index: VariantIdx, ) { let layout = place.layout(); - if layout.for_variant(fx, variant_index).is_uninhabited() { + if layout.is_variant_uninhabited(variant_index) { return; } match layout.variants { diff --git a/compiler/rustc_codegen_ssa/src/mir/place.rs b/compiler/rustc_codegen_ssa/src/mir/place.rs index b592e4a339346..14a5f71fbceaa 100644 --- a/compiler/rustc_codegen_ssa/src/mir/place.rs +++ b/compiler/rustc_codegen_ssa/src/mir/place.rs @@ -477,7 +477,7 @@ pub(super) fn codegen_tag_value<'tcx, V>( ) -> Result, UninhabitedVariantError> { // By checking uninhabited-ness first we don't need to worry about types // like `(u32, !)` which are single-variant but weird. - if layout.for_variant(cx, variant_index).is_uninhabited() { + if layout.is_variant_uninhabited(variant_index) { return Err(UninhabitedVariantError); } diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index a1776c6ba3d13..9d0499102c08e 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -210,7 +210,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Reading the discriminant of an uninhabited variant is UB. This is the basis for the // `uninhabited_enum_branching` MIR pass. It also ensures consistency with // `write_discriminant`. - if op.layout().for_variant(self, index).is_uninhabited() { + if op.layout().is_variant_uninhabited(index) { throw_ub!(UninhabitedEnumVariantRead(Some(index))) } interp_ok(index) @@ -252,7 +252,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Therefore, there's no way to represent those variants in the given layout. // Essentially, uninhabited variants do not have a tag that corresponds to their // discriminant, so we have to bail out here. - if layout.for_variant(self, variant_index).is_uninhabited() { + if layout.is_variant_uninhabited(variant_index) { throw_ub!(UninhabitedEnumVariantWritten(variant_index)) } From 187cf5d5776d0099ce2040e417b937c10c6bc7a4 Mon Sep 17 00:00:00 2001 From: Moulins Date: Wed, 5 Aug 2026 03:33:02 +0200 Subject: [PATCH 29/42] Add a doc comment to `TyAndLayout` discouraging its uses when possible --- compiler/rustc_abi/src/layout/ty.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index c5d8d758c4733..11aa18cdb224d 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -126,6 +126,13 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug + std::fmt::Display { } impl<'a, Ty> TyAndLayout<'a, Ty> { + /// Synthetize a layout representing the variant-specific fields of an enum-like layout. + /// + /// Note that the resulting layout *does not* fully describes `self.ty` at that specific + /// variant: prefix fields (e.g. in coroutines) and tag information are lost. + /// + /// If you don't need type information about the variant's fields, prefer using + /// `self.layout.variants` directly. pub fn for_variant(self, cx: &C, variant_index: VariantIdx) -> Self where Ty: TyAbiInterface<'a, C>, From 3a7ae70ed7f494e7b19560ecc24e2c6a06aea027 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 5 Aug 2026 15:09:38 +0200 Subject: [PATCH 30/42] use `VisitorResult` helper macros .. instead of hand-rolling our own --- .../src/ty/context/impl_interner.rs | 8 ++----- compiler/rustc_middle/src/ty/trait_def.rs | 24 ++++++------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 983b4afefdb5f..03c5ee80f6645 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -1,6 +1,5 @@ //! Implementation of [`rustc_type_ir::Interner`] for [`TyCtxt`]. -use std::ops::ControlFlow; use std::{debug_assert_matches, fmt}; use rustc_data_structures::Limit; @@ -14,7 +13,7 @@ use rustc_span::{DUMMY_SP, Span, Symbol}; use rustc_type_ir::lang_items::{SolverAdtLangItem, SolverProjectionLangItem, SolverTraitLangItem}; use rustc_type_ir::{ BoundVar, CollectAndApply, DebruijnIndex, Interner, TypeFoldable, Unnormalized, VisitorResult, - search_graph, + search_graph, try_visit, }; use crate::dep_graph::{DepKind, DepNodeIndex}; @@ -560,10 +559,7 @@ impl<'tcx> Interner for TyCtxt<'tcx> { ) -> R { let trait_impls = self.trait_impls_of(trait_def_id); for &impl_def_id in trait_impls.blanket_impls() { - match f(impl_def_id).branch() { - ControlFlow::Break(b) => return R::from_residual(b), - ControlFlow::Continue(()) => {} - } + try_visit!(f(impl_def_id)); } R::output() diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index 3b0d78d34af76..07b772217ccac 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -1,5 +1,4 @@ use std::iter; -use std::ops::ControlFlow; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::ErrorGuaranteed; @@ -13,7 +12,7 @@ use tracing::debug; use crate::query::LocalCrate; use crate::traits::specialization_graph; use crate::ty::fast_reject::{self, SimplifiedType, TreatParams}; -use crate::ty::{self, Ident, Interner, RestrictionKind, Ty, TyCtxt, VisitorResult}; +use crate::ty::{self, Ident, Interner, RestrictionKind, Ty, TyCtxt, VisitorResult, try_visit}; /// A trait's definition with type information. #[derive(StableHash, Encodable, Decodable)] @@ -142,21 +141,12 @@ impl<'tcx> TyCtxt<'tcx> { self_ty: Ty<'tcx>, mut f: impl FnMut(DefId) -> R, ) -> R { - macro_rules! ret { - ($e: expr) => { - match $e.branch() { - ControlFlow::Break(b) => return R::from_residual(b), - ControlFlow::Continue(()) => {} - } - }; - } - let tcx = self; let trait_impls = tcx.trait_impls_of(trait_def_id); let mut consider_impls_for_simplified_type = |simp| { if let Some(impls_for_type) = trait_impls.non_blanket_impls().get(&simp) { for &impl_def_id in impls_for_type { - ret!(f(impl_def_id)) + try_visit!(f(impl_def_id)) } } @@ -191,7 +181,7 @@ impl<'tcx> TyCtxt<'tcx> { ty::fast_reject::TreatParams::AsRigid, ) .unwrap(); - ret!(consider_impls_for_simplified_type(simp)); + try_visit!(consider_impls_for_simplified_type(simp)); } // HACK: For integer and float variables we have to manually look at all impls @@ -219,7 +209,7 @@ impl<'tcx> TyCtxt<'tcx> { ty::SimplifiedType::Uint(Usize), ]; for simp in possible_integers { - ret!(consider_impls_for_simplified_type(simp)); + try_visit!(consider_impls_for_simplified_type(simp)); } } @@ -234,7 +224,7 @@ impl<'tcx> TyCtxt<'tcx> { ]; for simp in possible_floats { - ret!(consider_impls_for_simplified_type(simp)); + try_visit!(consider_impls_for_simplified_type(simp)); } } @@ -245,14 +235,14 @@ impl<'tcx> TyCtxt<'tcx> { self_ty, ty::fast_reject::TreatParams::AsRigid, ) { - ret!(consider_impls_for_simplified_type(simp)); + try_visit!(consider_impls_for_simplified_type(simp)); } } // This is only for diagnostics and normally ty vars should be handled by the callers. ty::Infer(ty::TyVar(_)) => { for &impl_def_id in trait_impls.non_blanket_impls().values().flatten() { - ret!(f(impl_def_id)); + try_visit!(f(impl_def_id)); } } From be9602caf76a0f4856eabea22d1b2178f7216b14 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 5 Aug 2026 19:16:30 +0300 Subject: [PATCH 31/42] expand: Feature gate AST-based attribute macros on expressions and non-item statements --- compiler/rustc_expand/src/expand.rs | 1 + tests/ui/cfg/cfg-stmt-recovery.rs | 2 +- .../invalid-node-range-issue-129166.rs | 2 +- tests/ui/eii/errors.rs | 2 +- tests/ui/eii/errors.stderr | 13 ++++++++++++- tests/ui/macros/issue-111749.rs | 1 + tests/ui/macros/issue-111749.stderr | 13 ++++++++++++- tests/ui/proc-macro/cfg-eval-fail.rs | 1 + tests/ui/proc-macro/cfg-eval-fail.stderr | 13 ++++++++++++- .../ui/proc-macro/derive-macro-invalid-placement.rs | 2 +- 10 files changed, 43 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 045233c0c4d21..4846b48af8d5e 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -858,6 +858,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } } else if let SyntaxExtensionKind::LegacyAttr(expander) = ext { + self.gate_proc_macro_attr_item(span, &item); // `LegacyAttr` is only used for builtin attribute macros, which have their // safety checked by `check_builtin_meta_item`, so we don't need to check // `unsafety` here. diff --git a/tests/ui/cfg/cfg-stmt-recovery.rs b/tests/ui/cfg/cfg-stmt-recovery.rs index f0f9a649165b5..98f79cd8cfc1c 100644 --- a/tests/ui/cfg/cfg-stmt-recovery.rs +++ b/tests/ui/cfg/cfg-stmt-recovery.rs @@ -1,7 +1,7 @@ // Verify that we do not ICE when failing to parse a statement in `cfg_eval`. #![feature(cfg_eval)] -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] #[cfg_eval] fn main() { diff --git a/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs b/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs index 7c42be3ed4d6e..3f6f902cf3688 100644 --- a/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs +++ b/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs @@ -3,7 +3,7 @@ //@ check-pass #![feature(cfg_eval)] -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] fn f() -> u32 { #[cfg_eval] #[cfg(not(FALSE))] 0 diff --git a/tests/ui/eii/errors.rs b/tests/ui/eii/errors.rs index bc6c17f463a78..3b28e268662ef 100644 --- a/tests/ui/eii/errors.rs +++ b/tests/ui/eii/errors.rs @@ -8,7 +8,7 @@ #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros fn hello() { #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros - let x = 3 + 3; + let x = 3 + 3; //~| ERROR custom attributes cannot be applied to statements } #[eii_declaration] //~ ERROR `#[eii_declaration(...)]` expects a list of one or two elements diff --git a/tests/ui/eii/errors.stderr b/tests/ui/eii/errors.stderr index 553ae622cb36f..512cd135de4c3 100644 --- a/tests/ui/eii/errors.stderr +++ b/tests/ui/eii/errors.stderr @@ -4,6 +4,16 @@ error: `#[eii_declaration(...)]` is only valid on macros LL | #[eii_declaration(bar)] | ^^^^^^^^^^^^^^^^^^^^^^^ +error[E0658]: custom attributes cannot be applied to statements + --> $DIR/errors.rs:10:5 + | +LL | #[eii_declaration(bar)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: `#[eii_declaration(...)]` is only valid on macros --> $DIR/errors.rs:10:5 | @@ -88,5 +98,6 @@ error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` LL | #[foo = "default"] | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/macros/issue-111749.rs b/tests/ui/macros/issue-111749.rs index f009a69fe2535..799fee22685ab 100644 --- a/tests/ui/macros/issue-111749.rs +++ b/tests/ui/macros/issue-111749.rs @@ -9,4 +9,5 @@ fn main() { //~^ ERROR the `test` attribute may only be used on a free function //~| ERROR attribute must be of the form `#[test]` //~| WARNING this was previously accepted by the compiler but is being phased out + //~| ERROR custom attributes cannot be applied to expressions } diff --git a/tests/ui/macros/issue-111749.stderr b/tests/ui/macros/issue-111749.stderr index 267f939602b5b..f2773e7029ab5 100644 --- a/tests/ui/macros/issue-111749.stderr +++ b/tests/ui/macros/issue-111749.stderr @@ -1,3 +1,13 @@ +error[E0658]: custom attributes cannot be applied to expressions + --> $DIR/issue-111749.rs:8:17 + | +LL | cbor_map! { #[test(test)] 4i32}; + | ^^^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: the `test` attribute may only be used on a free function --> $DIR/issue-111749.rs:8:17 | @@ -20,8 +30,9 @@ LL | cbor_map! { #[test(test)] 4i32}; = note: for more information, see issue #57571 = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0658`. Future incompatibility report: Future breakage diagnostic: error: attribute must be of the form `#[test]` --> $DIR/issue-111749.rs:8:17 diff --git a/tests/ui/proc-macro/cfg-eval-fail.rs b/tests/ui/proc-macro/cfg-eval-fail.rs index a94dcd2837811..2cde895f2ea44 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.rs +++ b/tests/ui/proc-macro/cfg-eval-fail.rs @@ -4,4 +4,5 @@ fn main() { let _ = #[cfg_eval] #[cfg(false)] 0; //~^ ERROR removing an expression is not supported in this position + //~| ERROR custom attributes cannot be applied to expressions } diff --git a/tests/ui/proc-macro/cfg-eval-fail.stderr b/tests/ui/proc-macro/cfg-eval-fail.stderr index 7f21e4646b1cc..61da346fa69f6 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.stderr +++ b/tests/ui/proc-macro/cfg-eval-fail.stderr @@ -4,5 +4,16 @@ error: removing an expression is not supported in this position LL | let _ = #[cfg_eval] #[cfg(false)] 0; | ^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error[E0658]: custom attributes cannot be applied to expressions + --> $DIR/cfg-eval-fail.rs:5:13 + | +LL | let _ = #[cfg_eval] #[cfg(false)] 0; + | ^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/proc-macro/derive-macro-invalid-placement.rs b/tests/ui/proc-macro/derive-macro-invalid-placement.rs index fd24bd7284a92..463e7dc758505 100644 --- a/tests/ui/proc-macro/derive-macro-invalid-placement.rs +++ b/tests/ui/proc-macro/derive-macro-invalid-placement.rs @@ -1,6 +1,6 @@ //! regression test for -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] fn foo<#[derive(Debug)] T>() { //~ ERROR expected non-macro attribute, found attribute macro match 0 { From 640032da529931144e4a38b749bf02a551f72bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zal=C3=A1n=20B=C3=A1lint=20L=C3=A9vai?= Date: Mon, 3 Aug 2026 20:42:24 +0100 Subject: [PATCH 32/42] fix: Check the fallback map before queueing child in `visible_parent_map` BFS --- .../src/rmeta/decoder/cstore_impl.rs | 30 +++++++++++++------ .../direct-dep-with-multiple-reexports.rs | 15 ++++++++++ .../use-shortest-hidden-reexport-path.rs | 16 ++++++++++ .../use-shortest-hidden-reexport-path.stderr | 23 ++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 tests/ui/suggestions/suggest-path-through-direct-dep-crate/auxiliary/direct-dep-with-multiple-reexports.rs create mode 100644 tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.rs create mode 100644 tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.stderr diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 97cc76d833e9e..a9d524711a141 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::mem; use std::sync::Arc; +use rustc_data_structures::unord::ExtendUnord; use rustc_hir::attrs::Deprecation; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LOCAL_CRATE}; @@ -472,7 +473,7 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { // the former. // This is a rudimentary check that does not catch all cases, // just the easiest. - let mut fallback_map: Vec<(DefId, DefId)> = Default::default(); + let mut fallback_map: DefIdMap = Default::default(); // Issue 46112: We want the map to prefer the shortest // paths when reporting the path to an item. Therefore we @@ -533,14 +534,24 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { } } Entry::Vacant(entry) => { + if !fallback { + entry.insert(parent); + } + + // Make sure that we have not already explored this child + // through a previous fallback entry further up the BFS, + // in which case we do not want to put it back into the BFS queue, + // nor record a new fallback parent. + if fallback_map.contains_key(&def_id) { + return; + } + if fallback { // We do all of the same steps to fallback entries as to // preferred entries, except for recording them in a separate map. // It is important to not return early in the fallback cases to // ensure that we extend the BFS to the children of fallback items. - fallback_map.push((def_id, parent)); - } else { - entry.insert(parent); + fallback_map.insert(def_id, parent); } if child.res.module_like_def_id().is_some() { @@ -560,12 +571,13 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { // Fill in any missing entries with the less preferable path. // If this path re-exports the child as `_`, we still use this // path in a diagnostic that suggests importing `::*`. + // We must extend the fallback map with items from the visible parent map + // as the extend call overrides existing entries from the latter map, + // which we prefer over fallback entries. + let mut merged_visible_parent_map = fallback_map; + merged_visible_parent_map.extend_unord(visible_parent_map.into_items()); - for (child, parent) in fallback_map { - visible_parent_map.entry(child).or_insert(parent); - } - - visible_parent_map + merged_visible_parent_map }, dependency_formats: |tcx, ()| Arc::new(crate::dependency_format::calculate(tcx)), diff --git a/tests/ui/suggestions/suggest-path-through-direct-dep-crate/auxiliary/direct-dep-with-multiple-reexports.rs b/tests/ui/suggestions/suggest-path-through-direct-dep-crate/auxiliary/direct-dep-with-multiple-reexports.rs new file mode 100644 index 0000000000000..8ff8d3b572741 --- /dev/null +++ b/tests/ui/suggestions/suggest-path-through-direct-dep-crate/auxiliary/direct-dep-with-multiple-reexports.rs @@ -0,0 +1,15 @@ +#![crate_type = "lib"] + +extern crate transitive_dep; + +mod private { + pub use crate::transitive_dep::Struct; +} + +#[doc(hidden)] +pub use crate::private::*; + +#[doc(hidden)] +pub mod __private { + pub use crate::private::*; +} diff --git a/tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.rs b/tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.rs new file mode 100644 index 0000000000000..c3ec780429376 --- /dev/null +++ b/tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.rs @@ -0,0 +1,16 @@ +//@ aux-build: transitive-dep.rs +//@ aux-build: direct-dep-with-multiple-reexports.rs + +extern crate direct_dep_with_multiple_reexports as direct_dep; + +struct Struct; +//~^ NOTE `Struct` is defined in the current crate + +fn main() { + let _: direct_dep::Struct = Struct; + //~^ ERROR mismatched types + //~| NOTE expected `direct_dep::Struct`, found `Struct` + //~| NOTE expected due to this + //~| NOTE `Struct` and `direct_dep::Struct` have similar names, but are actually distinct types + //~| NOTE `direct_dep::Struct` is defined in crate `transitive_dep` +} diff --git a/tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.stderr b/tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.stderr new file mode 100644 index 0000000000000..46042907b38d7 --- /dev/null +++ b/tests/ui/suggestions/suggest-path-through-direct-dep-crate/use-shortest-hidden-reexport-path.stderr @@ -0,0 +1,23 @@ +error[E0308]: mismatched types + --> $DIR/use-shortest-hidden-reexport-path.rs:10:33 + | +LL | let _: direct_dep::Struct = Struct; + | ------------------ ^^^^^^ expected `direct_dep::Struct`, found `Struct` + | | + | expected due to this + | + = note: `Struct` and `direct_dep::Struct` have similar names, but are actually distinct types +note: `Struct` is defined in the current crate + --> $DIR/use-shortest-hidden-reexport-path.rs:6:1 + | +LL | struct Struct; + | ^^^^^^^^^^^^^ +note: `direct_dep::Struct` is defined in crate `transitive_dep` + --> $DIR/auxiliary/transitive-dep.rs:3:1 + | +LL | pub struct Struct; + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From cd8a97b4b21a9c2f8fa1ec9eed2cc6c83c489ddb Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:38:32 +0000 Subject: [PATCH 33/42] Handle LLVM 21 in memchr result codegen test LLVM 21 preserves the bounds assumption but does not eliminate the aggregate phi that LLVM 22 removes. Check each version's supported optimization and restore the shared postcondition so direct callers can eliminate bounds checks. --- library/core/src/slice/memchr.rs | 14 +++----------- .../lib-optimizations/memchr-result.rs | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 68826ecac31f3..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,18 +24,10 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - let result = memchr_naive(x, text); - if let Some(index) = result { - // SAFETY: `memchr_naive` only returns an index from within `text`. - unsafe { crate::hint::assert_unchecked(index < text.len()) }; - } - return result; - } - - let result = memchr_aligned(x, text); + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns an index from within `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index 77abc33adde83..beeab470c08af 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,22 +1,34 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false //@ only-x86_64 +//@ revisions: llvm-old llvm-new +//@ [llvm-old] max-llvm-major-version: 21 +//@ [llvm-new] min-llvm-version: 22 #![crate_type = "lib"] #![feature(slice_internals)] extern crate core; -use core::slice::memchr::memrchr; +use core::slice::memchr::{memchr, memrchr}; // CHECK-LABEL: @find_char #[no_mangle] pub fn find_char(haystack: &str, needle: char) -> Option { - // CHECK-NOT: phi { i64, i64 } + // llvm-old: call void @llvm.assume + // llvm-new-NOT: phi { i64, i64 } // CHECK: ret { i64, i64 } haystack.find(needle) } +// CHECK-LABEL: @find_byte +#[no_mangle] +pub fn find_byte(haystack: &[u8], needle: u8) -> Option { + // llvm-new-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memchr(needle, haystack).map(|index| haystack[index]) +} + // CHECK-LABEL: @rfind_byte #[no_mangle] pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { From 354cedd2a4a1c578ebde778d943625103bf7fd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Wed, 5 Aug 2026 21:50:19 +0200 Subject: [PATCH 34/42] add test showing difference between datalog polonius and alpha these are slightly distinct subsets of a platonic ideal borrowck. --- ...nll-legacy-unnecessary-error.legacy.stderr | 14 +++++++++++ .../nll-legacy-unnecessary-error.nll.stderr | 14 +++++++++++ .../polonius/nll-legacy-unnecessary-error.rs | 25 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 tests/ui/nll/polonius/nll-legacy-unnecessary-error.legacy.stderr create mode 100644 tests/ui/nll/polonius/nll-legacy-unnecessary-error.nll.stderr create mode 100644 tests/ui/nll/polonius/nll-legacy-unnecessary-error.rs diff --git a/tests/ui/nll/polonius/nll-legacy-unnecessary-error.legacy.stderr b/tests/ui/nll/polonius/nll-legacy-unnecessary-error.legacy.stderr new file mode 100644 index 0000000000000..79f32e55559cf --- /dev/null +++ b/tests/ui/nll/polonius/nll-legacy-unnecessary-error.legacy.stderr @@ -0,0 +1,14 @@ +error[E0506]: cannot assign to `z` because it is borrowed + --> $DIR/nll-legacy-unnecessary-error.rs:20:5 + | +LL | x.0 = &z; + | -- `z` is borrowed here +LL | z += 1; + | ^^^^^^ `z` is assigned to here but it was already borrowed +... +LL | dbg!(y.0); + | --- borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/nll/polonius/nll-legacy-unnecessary-error.nll.stderr b/tests/ui/nll/polonius/nll-legacy-unnecessary-error.nll.stderr new file mode 100644 index 0000000000000..79f32e55559cf --- /dev/null +++ b/tests/ui/nll/polonius/nll-legacy-unnecessary-error.nll.stderr @@ -0,0 +1,14 @@ +error[E0506]: cannot assign to `z` because it is borrowed + --> $DIR/nll-legacy-unnecessary-error.rs:20:5 + | +LL | x.0 = &z; + | -- `z` is borrowed here +LL | z += 1; + | ^^^^^^ `z` is assigned to here but it was already borrowed +... +LL | dbg!(y.0); + | --- borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/nll/polonius/nll-legacy-unnecessary-error.rs b/tests/ui/nll/polonius/nll-legacy-unnecessary-error.rs new file mode 100644 index 0000000000000..06a4f9b1a5640 --- /dev/null +++ b/tests/ui/nll/polonius/nll-legacy-unnecessary-error.rs @@ -0,0 +1,25 @@ +// NLLs and legacy polonius emit an unnecessary error here, unlike the alpha. It's not clear +// *exactly* why the datalog implementation rejects this, but it looks like it propagates the loan +// from 'x to 'y very eagerly, even though x is dead before the assignment. The loan would thus be +// live and invalidated by the assignment, AKA an error. + +//@ ignore-compare-mode-polonius (explicit revisions) +//@ revisions: nll polonius legacy +//@ [nll] compile-flags: -Z polonius=off +//@ [polonius] check-pass +//@ [polonius] compile-flags: -Z polonius=next +//@ [legacy] compile-flags: -Z polonius=legacy + +fn main() { + let mut x: (&u32,) = (&1,); + let mut y: (&u32,) = (&2,); + let mut z = 3; + + y.0 = x.0; + x.0 = &z; + z += 1; + //[nll]~^ ERROR: cannot assign to `z` because it is borrowed + //[legacy]~^^ ERROR: cannot assign to `z` because it is borrowed + + dbg!(y.0); +} From 0a0f6df1d5258c7de0a4f19702cd00f01f301ac5 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 6 Aug 2026 09:23:24 +0800 Subject: [PATCH 35/42] Rename `#[unroll]` => `#[rustc_unroll]` to mitigate nameres ambiguity Mitigation for [RUST-159429]. The recurring problem is that built-in attributes are treated differently compared to ordinary prelude attributes, built-in attributes, even while unstable, can name-collide with stable macro re-exports of the same name (and proc-macro helper attributes of the same name), which can break stable code. See [RUST-134964]. [RUST-159429]: https://github.com/rust-lang/rust/issues/159429 [RUST-134963]: https://github.com/rust-lang/rust/issues/134963 --- compiler/rustc_attr_parsing/src/attributes/unroll.rs | 3 ++- compiler/rustc_feature/src/builtin_attrs.rs | 6 ++++-- compiler/rustc_hir/src/attrs/data_structures.rs | 3 ++- compiler/rustc_span/src/symbol.rs | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/unroll.rs b/compiler/rustc_attr_parsing/src/attributes/unroll.rs index 3438fc044ec55..5a49feca2a1ea 100644 --- a/compiler/rustc_attr_parsing/src/attributes/unroll.rs +++ b/compiler/rustc_attr_parsing/src/attributes/unroll.rs @@ -6,7 +6,8 @@ use super::prelude::*; pub(crate) struct UnrollParser; impl SingleAttributeParser for UnrollParser { - const PATH: &[Symbol] = &[sym::unroll]; + // FIXME(#159429): temporarily renamed to mitigate `#[unroll]` nameres ambiguity. + const PATH: &[Symbol] = &[sym::rustc_unroll]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[ Allow(Target::Loop), Allow(Target::ForLoop), diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 72b51ad204b9d..bc6f87a2a7f17 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -217,10 +217,12 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ // - https://github.com/rust-lang/rust/issues/153629 sym::rustc_splat, - // The `#[unroll]` attribute. + // The `#[rustc_unroll]` attribute. // // - https://github.com/rust-lang/rust/pull/156816 - sym::unroll, + // + // FIXME(#159429): temporarily renamed to mitigate `#[unroll]` nameres ambiguity + sym::rustc_unroll, // `#[instrument_fn = "on|off"]` to insert or inhibit instrumentation function // calls inside a function, usually around the prologue. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 530483e87329c..94241e6a31eb0 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1706,7 +1706,8 @@ pub enum AttributeKind { limit: Limit, }, - /// Represents `#[unroll]` + /// Represents `#[rustc_unroll]` + // FIXME(#159429): temporarily renamed from `#[unroll]` to mitigate nameres ambiguity Unroll(UnrollAttr), /// Represents `#[unstable_feature_bound]`. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ff1d4253c4414..a346a5216128b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1872,6 +1872,8 @@ symbols! { rustc_test_marker, rustc_then_this_would_need, rustc_trivial_field_reads, + // FIXME(#159429): temporary rename to avoid `#[unroll]` nameres ambiguity + rustc_unroll, rustdoc, rustdoc_internals, rustdoc_missing_doc_code_examples, @@ -2254,7 +2256,6 @@ symbols! { unreachable_display, unreachable_macro, unrestricted_attribute_tokens, - unroll, unsafe_attributes, unsafe_binders, unsafe_block_in_unsafe_fn, From 44d291e788e84e53ab6afef2ca074183865e6a0c Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 30 Jul 2026 19:21:21 +0800 Subject: [PATCH 36/42] Update attr name in `#[unroll]` codegen-llvm tests --- tests/codegen-llvm/loop-attrs/unroll-for-metadata.rs | 8 ++++---- tests/codegen-llvm/loop-attrs/unroll-for-works.rs | 6 +++--- tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs | 8 ++++---- tests/codegen-llvm/loop-attrs/unroll-while-metadata.rs | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/codegen-llvm/loop-attrs/unroll-for-metadata.rs b/tests/codegen-llvm/loop-attrs/unroll-for-metadata.rs index 64113fbeb3247..60f9b6da6c9fe 100644 --- a/tests/codegen-llvm/loop-attrs/unroll-for-metadata.rs +++ b/tests/codegen-llvm/loop-attrs/unroll-for-metadata.rs @@ -15,7 +15,7 @@ unsafe extern "C" { pub fn unroll_hint() { // CHECK-LABEL: @unroll_hint // CHECK: !llvm.loop ![[HINT:[0-9]+]] - #[unroll] + #[rustc_unroll] for _ in 0..10 { unsafe { maybe_has_side_effect() } } @@ -25,7 +25,7 @@ pub fn unroll_hint() { pub fn unroll_full() { // CHECK-LABEL: @unroll_full // CHECK: !llvm.loop ![[FULL:[0-9]+]] - #[unroll(full)] + #[rustc_unroll(full)] for _ in 0..10 { unsafe { maybe_has_side_effect() } } @@ -35,7 +35,7 @@ pub fn unroll_full() { pub fn unroll_never() { // CHECK-LABEL: @unroll_never // CHECK: !llvm.loop ![[DISABLE:[0-9]+]] - #[unroll(never)] + #[rustc_unroll(never)] for _ in 0..10 { unsafe { maybe_has_side_effect() } } @@ -45,7 +45,7 @@ pub fn unroll_never() { pub fn unroll_count() { // CHECK-LABEL: @unroll_count // CHECK: !llvm.loop ![[COUNT:[0-9]+]] - #[unroll(5)] + #[rustc_unroll(5)] for _ in 0..10 { unsafe { maybe_has_side_effect() } } diff --git a/tests/codegen-llvm/loop-attrs/unroll-for-works.rs b/tests/codegen-llvm/loop-attrs/unroll-for-works.rs index b2f8b58c93573..0aa8d805c4f68 100644 --- a/tests/codegen-llvm/loop-attrs/unroll-for-works.rs +++ b/tests/codegen-llvm/loop-attrs/unroll-for-works.rs @@ -11,7 +11,7 @@ unsafe extern "C" { pub fn unroll_full() { // CHECK-LABEL: @unroll_full // CHECK-COUNT-512: tail call void @maybe_has_side_effect() - #[unroll(full)] + #[rustc_unroll(full)] for _ in 0..512 { unsafe { maybe_has_side_effect() } } @@ -22,7 +22,7 @@ pub fn unroll_never() { // CHECK-LABEL: @unroll_never // CHECK: tail call void @maybe_has_side_effect() // CHECK-NOT: tail call void @maybe_has_side_effect() - #[unroll(never)] + #[rustc_unroll(never)] for _ in 0..3 { unsafe { maybe_has_side_effect() } } @@ -32,7 +32,7 @@ pub fn unroll_never() { pub fn unroll_count() { // CHECK-LABEL: @unroll_count // CHECK-COUNT-5: tail call void @maybe_has_side_effect() - #[unroll(5)] + #[rustc_unroll(5)] for _ in 0..10 { unsafe { maybe_has_side_effect() } } diff --git a/tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs b/tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs index 2b2b0779cf49e..7b715d1ac1e32 100644 --- a/tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs +++ b/tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs @@ -17,7 +17,7 @@ pub fn unroll_hint() { // CHECK-LABEL: @unroll_hint // CHECK: !llvm.loop ![[HINT:[0-9]+]] let mut i = 0; - #[unroll] + #[rustc_unroll] loop { unsafe { maybe_has_side_effect() } i += 1; @@ -35,7 +35,7 @@ pub fn unroll_full() { // CHECK-LABEL: @unroll_full // CHECK: !llvm.loop ![[FULL:[0-9]+]] let mut i = 0; - let _return = (#[unroll(full)] + let _return = (#[rustc_unroll(full)] loop { unsafe { maybe_has_side_effect() } i += 1; @@ -50,7 +50,7 @@ pub fn unroll_never() { // CHECK-LABEL: @unroll_never // CHECK: !llvm.loop ![[DISABLE:[0-9]+]] let mut i = 0; - let _return = (1 + #[unroll(never)] + let _return = (1 + #[rustc_unroll(never)] loop { unsafe { maybe_has_side_effect() } i += 1; @@ -65,7 +65,7 @@ pub fn unroll_count() { // CHECK-LABEL: @unroll_count // CHECK: !llvm.loop ![[COUNT:[0-9]+]] let mut i = 0; - #[unroll(5)] + #[rustc_unroll(5)] loop { unsafe { maybe_has_side_effect() } i += 1; diff --git a/tests/codegen-llvm/loop-attrs/unroll-while-metadata.rs b/tests/codegen-llvm/loop-attrs/unroll-while-metadata.rs index c40a4188334e8..1a100aae1e717 100644 --- a/tests/codegen-llvm/loop-attrs/unroll-while-metadata.rs +++ b/tests/codegen-llvm/loop-attrs/unroll-while-metadata.rs @@ -16,7 +16,7 @@ pub fn unroll_hint() { // CHECK-LABEL: @unroll_hint // CHECK: !llvm.loop ![[HINT:[0-9]+]] let mut i = 0; - #[unroll] + #[rustc_unroll] while i < 10 { unsafe { maybe_has_side_effect() } i += 1; @@ -28,7 +28,7 @@ pub fn unroll_full() { // CHECK-LABEL: @unroll_full // CHECK: !llvm.loop ![[FULL:[0-9]+]] let mut i = 0; - #[unroll(full)] + #[rustc_unroll(full)] while i < 10 { unsafe { maybe_has_side_effect() } i += 1; @@ -40,7 +40,7 @@ pub fn unroll_never() { // CHECK-LABEL: @unroll_never // CHECK: !llvm.loop ![[DISABLE:[0-9]+]] let mut i = 0; - #[unroll(never)] + #[rustc_unroll(never)] while i < 10 { unsafe { maybe_has_side_effect() } i += 1; @@ -52,7 +52,7 @@ pub fn unroll_count() { // CHECK-LABEL: @unroll_count // CHECK: !llvm.loop ![[COUNT:[0-9]+]] let mut i = 0; - #[unroll(5)] + #[rustc_unroll(5)] while i < 10 { unsafe { maybe_has_side_effect() } i += 1; From 91ee1f3a9b72a19d6c21bbcd34872fa24da08617 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 30 Jul 2026 19:21:53 +0800 Subject: [PATCH 37/42] Update attr name in `#[unroll]` ui tests To account for the renamed `#[rustc_unroll]` attribute. --- tests/ui/attributes/unroll/invalid-unroll.rs | 10 ++-- .../attributes/unroll/invalid-unroll.stderr | 50 +++++++++---------- .../feature-gates/feature-gate-loop-hints.rs | 2 +- .../feature-gate-loop-hints.stderr | 6 +-- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/ui/attributes/unroll/invalid-unroll.rs b/tests/ui/attributes/unroll/invalid-unroll.rs index 8696cefe818f7..13a14c2713fc1 100644 --- a/tests/ui/attributes/unroll/invalid-unroll.rs +++ b/tests/ui/attributes/unroll/invalid-unroll.rs @@ -2,18 +2,18 @@ #![crate_type = "lib"] pub fn main() { - #[unroll(please)] //~ ERROR malformed `unroll` attribute input + #[rustc_unroll(please)] //~ ERROR malformed `rustc_unroll` attribute input for _ in 0..10 {} - #[unroll("never")] //~ ERROR malformed `unroll` attribute input + #[rustc_unroll("never")] //~ ERROR malformed `rustc_unroll` attribute input for _ in 0..10 {} - #[unroll()] //~ ERROR malformed `unroll` attribute input + #[rustc_unroll()] //~ ERROR malformed `rustc_unroll` attribute input for _ in 0..10 {} - #[unroll(-1)] //~ ERROR expected a literal + #[rustc_unroll(-1)] //~ ERROR expected a literal for _ in 0..10 {} - #[unroll(1.5)] //~ ERROR malformed `unroll` attribute input + #[rustc_unroll(1.5)] //~ ERROR malformed `rustc_unroll` attribute input for _ in 0..10 {} } diff --git a/tests/ui/attributes/unroll/invalid-unroll.stderr b/tests/ui/attributes/unroll/invalid-unroll.stderr index 9d25fa2c42d66..ced0523bf99ea 100644 --- a/tests/ui/attributes/unroll/invalid-unroll.stderr +++ b/tests/ui/attributes/unroll/invalid-unroll.stderr @@ -1,46 +1,46 @@ -error[E0539]: malformed `unroll` attribute input +error[E0539]: malformed `rustc_unroll` attribute input --> $DIR/invalid-unroll.rs:5:7 | -LL | #[unroll(please)] - | ^^^^^^^------^ - | | - | valid arguments are `full` or `never` +LL | #[rustc_unroll(please)] + | ^^^^^^^^^^^^^------^ + | | + | valid arguments are `full` or `never` -error[E0539]: malformed `unroll` attribute input +error[E0539]: malformed `rustc_unroll` attribute input --> $DIR/invalid-unroll.rs:8:7 | -LL | #[unroll("never")] - | ^^^^^^^-------^ - | | - | valid arguments are `full` or `never` +LL | #[rustc_unroll("never")] + | ^^^^^^^^^^^^^-------^ + | | + | valid arguments are `full` or `never` -error[E0805]: malformed `unroll` attribute input +error[E0805]: malformed `rustc_unroll` attribute input --> $DIR/invalid-unroll.rs:11:7 | -LL | #[unroll()] - | ^^^^^^-- - | | - | expected an argument here +LL | #[rustc_unroll()] + | ^^^^^^^^^^^^-- + | | + | expected an argument here error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression - --> $DIR/invalid-unroll.rs:14:14 + --> $DIR/invalid-unroll.rs:14:20 | -LL | #[unroll(-1)] - | ^^ expressions are not allowed here +LL | #[rustc_unroll(-1)] + | ^^ expressions are not allowed here | help: negative numbers are not literals, try removing the `-` sign | -LL - #[unroll(-1)] -LL + #[unroll(1)] +LL - #[rustc_unroll(-1)] +LL + #[rustc_unroll(1)] | -error[E0539]: malformed `unroll` attribute input +error[E0539]: malformed `rustc_unroll` attribute input --> $DIR/invalid-unroll.rs:17:7 | -LL | #[unroll(1.5)] - | ^^^^^^^---^ - | | - | valid arguments are `full` or `never` +LL | #[rustc_unroll(1.5)] + | ^^^^^^^^^^^^^---^ + | | + | valid arguments are `full` or `never` error: aborting due to 5 previous errors diff --git a/tests/ui/feature-gates/feature-gate-loop-hints.rs b/tests/ui/feature-gates/feature-gate-loop-hints.rs index 85a1f10ab0a63..480d9a95f08a9 100644 --- a/tests/ui/feature-gates/feature-gate-loop-hints.rs +++ b/tests/ui/feature-gates/feature-gate-loop-hints.rs @@ -1,4 +1,4 @@ fn main() { - #[unroll] //~ ERROR the `unroll` attribute is an experimental feature + #[rustc_unroll] //~ ERROR the `rustc_unroll` attribute is an experimental feature for _ in 0..10 {} } diff --git a/tests/ui/feature-gates/feature-gate-loop-hints.stderr b/tests/ui/feature-gates/feature-gate-loop-hints.stderr index 98279fe144126..56c3ec6812c9c 100644 --- a/tests/ui/feature-gates/feature-gate-loop-hints.stderr +++ b/tests/ui/feature-gates/feature-gate-loop-hints.stderr @@ -1,8 +1,8 @@ -error[E0658]: the `unroll` attribute is an experimental feature +error[E0658]: the `rustc_unroll` attribute is an experimental feature --> $DIR/feature-gate-loop-hints.rs:2:7 | -LL | #[unroll] - | ^^^^^^ +LL | #[rustc_unroll] + | ^^^^^^^^^^^^ | = note: see issue #156874 for more information = help: add `#![feature(loop_hints)]` to the crate attributes to enable From 945d2f32788c3698b820201319955a4764ac2efb Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 30 Jul 2026 19:22:16 +0800 Subject: [PATCH 38/42] Rebless `rustc-attrs` feature gate test This stderr diff is a funny side-effect of renaming `#[unroll]` => `#[rustc_unroll]`, where the `#[rustc_unknown]` attribute name is just similar enough edit distance wise to `#[rustc_unroll]` that `#[rustc_unroll]` shows up as a plausible suggestion candidate, lol. --- tests/ui/feature-gates/feature-gate-rustc-attrs.stderr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ui/feature-gates/feature-gate-rustc-attrs.stderr b/tests/ui/feature-gates/feature-gate-rustc-attrs.stderr index 629d25ec4f01c..884a02c5ec25d 100644 --- a/tests/ui/feature-gates/feature-gate-rustc-attrs.stderr +++ b/tests/ui/feature-gates/feature-gate-rustc-attrs.stderr @@ -33,6 +33,12 @@ error: cannot find attribute `rustc_unknown` in this scope | LL | #[rustc_unknown] | ^^^^^^^^^^^^^ + | +help: a built-in attribute with a similar name exists + | +LL - #[rustc_unknown] +LL + #[rustc_unroll] + | error[E0658]: use of an internal attribute --> $DIR/feature-gate-rustc-attrs.rs:20:3 From d1265b7cfb2f221c80701bddf09d9614d076dcd3 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Thu, 30 Jul 2026 19:31:02 +0800 Subject: [PATCH 39/42] Update attr name for `#[unroll]` in Unstable Book --- .../src/language-features/loop-hints.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/doc/unstable-book/src/language-features/loop-hints.md b/src/doc/unstable-book/src/language-features/loop-hints.md index c02411d30c668..b82a7b367095a 100644 --- a/src/doc/unstable-book/src/language-features/loop-hints.md +++ b/src/doc/unstable-book/src/language-features/loop-hints.md @@ -6,18 +6,22 @@ The tracking issue for this feature is: [#156874] ------ + + Loop unrolling can be a powerful optimization but like inlining, it is sometimes useful to manually provide hints to optimizations. -`#[unroll]` will encourage unrolling of a loop. +`#[rustc_unroll]` will encourage unrolling of a loop. -`#[unroll(full)]` is a stronger hint and can cause optimizations to completely ignore the code +`#[rustc_unroll(full)]` is a stronger hint and can cause optimizations to completely ignore the code side growth from repeating a loop body. -`#[unroll(never)]` is a strong hint to not unroll the loop at all. Note that other loop +`#[rustc_unroll(never)]` is a strong hint to not unroll the loop at all. Note that other loop optimizations may still be applied. -`#[unroll(N)]` is a hint to unroll `N` iterations of the loop. +`#[rustc_unroll(N)]` is a hint to unroll `N` iterations of the loop. In all cases these are just hints and may be ignored. But unlike function inlining hints, loops tend to be heavily modified during compilation, which can make obeying hints challenging. From 39b3ad838343b8548ba65f2f05a5e44932bcf132 Mon Sep 17 00:00:00 2001 From: "advithkrishnan.eth" Date: Thu, 6 Aug 2026 01:23:28 +0530 Subject: [PATCH 40/42] Suggest if-let chain continuation on unclosed delimiter --- compiler/rustc_parse/src/lexer/diagnostics.rs | 8 +++++++ compiler/rustc_parse/src/lexer/tokentrees.rs | 9 ++++++++ tests/ui/parser/brace-in-let-chain.stderr | 22 +++++++++++++++++++ tests/ui/parser/deli-ident-issue-1.stderr | 4 +++- .../ui/parser/if-let-chain-unclosed-delim.rs | 8 +++++++ .../parser/if-let-chain-unclosed-delim.stderr | 17 ++++++++++++++ 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/ui/parser/if-let-chain-unclosed-delim.rs create mode 100644 tests/ui/parser/if-let-chain-unclosed-delim.stderr diff --git a/compiler/rustc_parse/src/lexer/diagnostics.rs b/compiler/rustc_parse/src/lexer/diagnostics.rs index 5c66d2be7dfdd..31c7d9af33bee 100644 --- a/compiler/rustc_parse/src/lexer/diagnostics.rs +++ b/compiler/rustc_parse/src/lexer/diagnostics.rs @@ -20,6 +20,10 @@ pub(super) struct TokenTreeDiagInfo { /// Collect empty block spans that might have been auto-inserted by editors. pub empty_block_spans: Vec, + /// Spans of `&&`/`||` tokens that directly open a brace-delimited block, + /// which usually means the user meant to continue an if-let chain. + pub if_let_chain_hint_spans: Vec, + /// Collect the spans of braces (Open, Close). Used only /// for detecting if blocks are empty and only braces. pub matching_block_spans: Vec<(Span, Span)>, @@ -124,6 +128,10 @@ pub(super) fn report_suspicious_mismatch_block( err.span_label(parent.1, "...matches this closing brace"); } } + + for span in diag_info.if_let_chain_hint_spans.iter() { + err.span_label(*span, "you might have meant to continue an if-let chain here"); + } } pub(crate) fn make_errors_for_mismatched_closing_delims<'psess>( diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index 757cd755bf65f..3455947471503 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -90,6 +90,15 @@ impl<'psess, 'src> Lexer<'psess, 'src> { self.diag_info.matching_block_spans.push((pre_span, close_delimiter_span)); } + // A brace-delimited block whose first token is `&&`/`||` usually means + // the user meant to continue an if-let chain, e.g. `if let P = e { && cond {`. + if Delimiter::Brace == open_delim + && let Some(TokenTree::Token(tok, _)) = tts.iter().next() + && matches!(tok.kind, token::AndAnd | token::OrOr) + { + self.diag_info.if_let_chain_hint_spans.push(tok.span); + } + // Move past the closing delimiter. self.bump_minimal() } else { diff --git a/tests/ui/parser/brace-in-let-chain.stderr b/tests/ui/parser/brace-in-let-chain.stderr index 12af95c278688..15622bd3266b2 100644 --- a/tests/ui/parser/brace-in-let-chain.stderr +++ b/tests/ui/parser/brace-in-let-chain.stderr @@ -4,24 +4,46 @@ error: this file contains an unclosed delimiter LL | fn main() { | - unclosed delimiter ... +LL | && let () = () + | -- you might have meant to continue an if-let chain here +... LL | fn quux() { | - unclosed delimiter ... +LL | && let () = () + | -- you might have meant to continue an if-let chain here +... LL | fn foobar() { | - unclosed delimiter ... +LL | && let () = () + | -- you might have meant to continue an if-let chain here +... LL | fn fubar() { | - unclosed delimiter ... +LL | && let () = () + | -- you might have meant to continue an if-let chain here +... LL | fn qux() { | - unclosed delimiter ... +LL | && let () = () + | -- you might have meant to continue an if-let chain here +... LL | fn foo() { | - another 3 unclosed delimiters begin from here +LL | { +LL | && let () = () + | -- you might have meant to continue an if-let chain here +... +LL | && let () = () + | -- you might have meant to continue an if-let chain here ... LL | { | - this delimiter might not be properly closed... LL | && let () = () + | -- you might have meant to continue an if-let chain here LL | } | - ...as it matches this but it has different indentation LL | } diff --git a/tests/ui/parser/deli-ident-issue-1.stderr b/tests/ui/parser/deli-ident-issue-1.stderr index d17913eb7ea40..7abe8b0ea5554 100644 --- a/tests/ui/parser/deli-ident-issue-1.stderr +++ b/tests/ui/parser/deli-ident-issue-1.stderr @@ -6,7 +6,9 @@ LL | impl dyn Demo { ... LL | && let Some(c) = num { | - this delimiter might not be properly closed... -... +LL | && b == c { + | -- you might have meant to continue an if-let chain here +LL | } LL | } | - ...as it matches this but it has different indentation ... diff --git a/tests/ui/parser/if-let-chain-unclosed-delim.rs b/tests/ui/parser/if-let-chain-unclosed-delim.rs new file mode 100644 index 0000000000000..11f365ce5311c --- /dev/null +++ b/tests/ui/parser/if-let-chain-unclosed-delim.rs @@ -0,0 +1,8 @@ +//! Regression test for an unclosed delimiter whose block begins with `&&`/`||` +//! should hint that the user may have meant to continue an if-let chain. +fn main() { + if let Some(x) = Some(42) { + && x == 42 + { + } +} //~ ERROR this file contains an unclosed delimiter diff --git a/tests/ui/parser/if-let-chain-unclosed-delim.stderr b/tests/ui/parser/if-let-chain-unclosed-delim.stderr new file mode 100644 index 0000000000000..ce34a89b62b5a --- /dev/null +++ b/tests/ui/parser/if-let-chain-unclosed-delim.stderr @@ -0,0 +1,17 @@ +error: this file contains an unclosed delimiter + --> $DIR/if-let-chain-unclosed-delim.rs:8:54 + | +LL | fn main() { + | - unclosed delimiter +LL | if let Some(x) = Some(42) { + | - this delimiter might not be properly closed... +LL | && x == 42 + | -- you might have meant to continue an if-let chain here +... +LL | } + | - ^ + | | + | ...as it matches this but it has different indentation + +error: aborting due to 1 previous error + From f207c166107417e252acfb03be7bc9c5db2be681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Thu, 6 Aug 2026 05:56:33 +0200 Subject: [PATCH 41/42] Avoid the std DLL copy alongside rustc --- src/bootstrap/src/core/build_steps/compile.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index de3029bc0e620..652e797538223 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2350,17 +2350,13 @@ impl CommandLineStep for Assemble { let is_dylib_or_debug = is_dylib(&f.path()) || is_debug_info(&filename); // If we link statically to stdlib, do not copy the libstd dynamic library file - // FIXME: Also do this for Windows once incremental post-optimization stage0 tests - // work without std.dll (see https://github.com/rust-lang/rust/pull/131188). - let can_be_rustc_dynamic_dep = if builder - .link_std_into_rustc_driver(target_compiler.host) - && !target_compiler.host.is_windows() - { - let is_std = filename.starts_with("std-") || filename.starts_with("libstd-"); - !is_std - } else { - true - }; + let can_be_rustc_dynamic_dep = + if builder.link_std_into_rustc_driver(target_compiler.host) { + let is_std = filename.starts_with("std-") || filename.starts_with("libstd-"); + !is_std + } else { + true + }; if is_dylib_or_debug && can_be_rustc_dynamic_dep && !is_proc_macro { builder.copy_link(&f.path(), &rustc_libdir.join(&filename), FileType::Regular); From 71b84560aa4685a87c7c77441a5dadf4784ea159 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 29 Jul 2026 16:37:26 +0200 Subject: [PATCH 42/42] check_consts: exhaustively match on CastKind --- .../src/check_consts/check.rs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index e0388f3cc7464..3c9629c1d551e 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -626,28 +626,38 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } Rvalue::Cast( - CastKind::PointerCoercion( + CastKind::IntToInt + | CastKind::FloatToInt + | CastKind::FloatToFloat + | CastKind::IntToFloat + | CastKind::PtrToPtr + | CastKind::FnPtrToPtr + | CastKind::Transmute + | CastKind::BoxDerefTransmute + | CastKind::PointerCoercion( PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer | PointerCoercion::UnsafeFnPointer | PointerCoercion::ClosureFnPointer(_) - | PointerCoercion::ReifyFnPointer(_), + | PointerCoercion::ReifyFnPointer(_) + | PointerCoercion::Unsize, _, ), _, _, ) => { - // These are all okay; they only change the type, not the data. + // Operations that are fully supported by const-eval. } - + // Special checks for special casts Rvalue::Cast(CastKind::PointerExposeProvenance, _, _) => { self.check_op(ops::RawPtrToIntCast); } Rvalue::Cast(CastKind::PointerWithExposedProvenance, _, _) => { // Since no pointer can ever get exposed (rejected above), this is easy to support. } - - Rvalue::Cast(_, _, _) => {} + Rvalue::Cast(kind @ CastKind::Subtype, _, _) => { + span_bug!(self.span, "invalid CastKind for this MIR phase: {kind:?}"); + } Rvalue::UnaryOp(op, operand) => { let ty = operand.ty(self.body, self.tcx);