From 752a91064f4297df39b7215bd9ba30bdd8145661 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 28 Apr 2026 17:23:46 +0200 Subject: [PATCH 01/28] MIR Call terminator: evaluate destination place before arguments --- compiler/rustc_const_eval/src/interpret/step.rs | 5 +++-- compiler/rustc_middle/src/mir/syntax.rs | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index bac03cf91d6e6..daffbdac47d90 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -541,16 +541,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let old_stack = self.frame_idx(); let old_loc = self.frame().loc; + // Evaluation order consistent with assignment: destination first. + let dest_place = self.eval_place(destination)?; let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = self.eval_callee_and_args(terminator, func, args, &destination)?; - let destination = self.eval_place(destination)?; self.init_fn_call( callee, (fn_sig.abi(), fn_abi), &args, with_caller_location, - &destination, + &dest_place, target, if fn_abi.can_unwind { unwind } else { mir::UnwindAction::Unreachable }, )?; diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 3d320d4cf8383..54ffebade0934 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -306,9 +306,9 @@ pub enum StatementKind<'tcx> { /// Assign statements roughly correspond to an assignment in Rust proper (`x = ...`) except /// without the possibility of dropping the previous value (that must be done separately, if at /// all). The *exact* way this works is undecided. It probably does something like evaluating - /// the LHS to a place and the RHS to a value, and then storing the value to the place. Various - /// parts of this may do type specific things that are more complicated than simply copying - /// bytes. In particular, the assignment will typically erase the contents of padding, + /// the LHS to a place, then the RHS to a value, and then storing the value to the place. + /// Various parts of this may do type specific things that are more complicated than simply + /// copying bytes. In particular, the assignment will typically erase the contents of padding, /// erase provenance from non-pointer types, and implicitly "retag" all references and boxes /// that it copies, meaning that the resulting value is not an exact duplicate for all intents /// and purposes of the original value. @@ -796,6 +796,9 @@ pub enum TerminatorKind<'tcx> { /// operands not aliasing the return place. It is unclear how this is justified in MIR, see /// [#71117]. /// + /// The evaluation order is currently "first compute destination place, then `func` operand, + /// then the arguments in left-to-right order". + /// /// [#71117]: https://github.com/rust-lang/rust/issues/71117 Call { /// The function that’s being called. @@ -1494,7 +1497,7 @@ pub enum CastKind { /// but running a transmute between differently-sized types is UB. Transmute, - /// A `Subtype` cast is applied to any `StatementKind::Assign` where + /// A `Subtype` cast is applied to any [`StatementKind::Assign`] where /// type of lvalue doesn't match the type of rvalue, the primary goal is making subtyping /// explicit during optimizations and codegen. /// From f6d98a43cf64f12ea502322b37c63213aa9e3068 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 30 May 2026 12:40:30 +0200 Subject: [PATCH 02/28] JSON target specs: remove 'x86-softfloat' compatibility alias --- compiler/rustc_target/src/spec/mod.rs | 2 +- src/tools/miri/tests/x86_64-unknown-kernel.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index ea6abe5491262..64f347ae15955 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -993,7 +993,7 @@ crate::target_spec_enum! { /// On x86-32 only: make use of SSE and SSE2 for ABI purposes. X86Sse2 = "x86-sse2", /// On x86-32/64, aarch64, and S390x: do not use any FPU or SIMD registers for the ABI. - Softfloat = "softfloat", "x86-softfloat", + Softfloat = "softfloat", } parse_error_type = "rustc abi"; diff --git a/src/tools/miri/tests/x86_64-unknown-kernel.json b/src/tools/miri/tests/x86_64-unknown-kernel.json index 0f8032c39d568..c5e519429d71a 100644 --- a/src/tools/miri/tests/x86_64-unknown-kernel.json +++ b/src/tools/miri/tests/x86_64-unknown-kernel.json @@ -10,7 +10,7 @@ "vendor": "unknown", "linker": "rust-lld", "linker-flavor": "gnu-lld", - "rustc-abi": "x86-softfloat", + "rustc-abi": "softfloat", "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float", "dynamic-linking": false, "executables": true, From f0ac18af5adf36355174889701c5835c0bdadcf0 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sat, 30 May 2026 13:09:44 +0200 Subject: [PATCH 03/28] CI: rfl: add temporary commit for removal of `x86-softfloat` The compiler is removing the `x86-softfloat` [1], thus temporarily add a patch. As usual, the patch will eventually make it to the Linux kernel so that both sides are good. Cc: Ralf Jung Cc: Alice Ryhl Link: https://github.com/rust-lang/rust/pull/157151 [1] Signed-off-by: Miguel Ojeda --- src/ci/docker/scripts/rfl-build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ci/docker/scripts/rfl-build.sh b/src/ci/docker/scripts/rfl-build.sh index 54ede153f6153..eae2aec917b1c 100755 --- a/src/ci/docker/scripts/rfl-build.sh +++ b/src/ci/docker/scripts/rfl-build.sh @@ -2,7 +2,8 @@ set -euo pipefail -LINUX_VERSION=v7.1-rc1 +# https://github.com/rust-lang/rust/pull/157151 +LINUX_VERSION=40bc55834bc15896f4438b0936c679ef32e20df1 # Build rustc, rustdoc, cargo, clippy-driver and rustfmt ../x.py build --stage 2 library rustdoc clippy rustfmt From b2d604c9085f718aa0667740c554374eb4ca783e Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 12 Jun 2026 16:55:44 -0300 Subject: [PATCH 04/28] Expand free alias types in the auto-trait orphan check The auto-trait branch of the orphan check inspected the unexpanded self type, so an impl whose self type was a free (lazy) type alias was rejected even when the alias resolved to an otherwise valid nominal type, e.g. `unsafe impl Sync for Alias {}` with `type Alias = Local;`. Expand free alias types before classifying the self type, mirroring the locality check above. Expansion still reveals genuinely problematic self types (trait objects, opaque types, type parameters), so those keep being rejected. --- .../src/coherence/orphan.rs | 8 ++- tests/ui/lazy-type-alias/auto-trait-impls.rs | 53 +++++++++++++++++++ .../lazy-type-alias/auto-trait-impls.stderr | 17 ++++++ .../impl_for_weak_alias.rs | 6 ++- .../impl_for_weak_alias.stderr | 11 ---- 5 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 tests/ui/lazy-type-alias/auto-trait-impls.rs create mode 100644 tests/ui/lazy-type-alias/auto-trait-impls.stderr delete mode 100644 tests/ui/type-alias-impl-trait/impl_for_weak_alias.stderr diff --git a/compiler/rustc_hir_analysis/src/coherence/orphan.rs b/compiler/rustc_hir_analysis/src/coherence/orphan.rs index bacc1f1549973..96f657f0963ce 100644 --- a/compiler/rustc_hir_analysis/src/coherence/orphan.rs +++ b/compiler/rustc_hir_analysis/src/coherence/orphan.rs @@ -83,7 +83,13 @@ pub(crate) fn orphan_check_impl( ); if tcx.trait_is_auto(trait_def_id) { - let self_ty = trait_ref.self_ty(); + // Expand free alias types (e.g. lazy type aliases) so that the checks below operate + // on the type the alias resolves to rather than the alias itself. Otherwise an impl + // whose self type is an alias expanding to an otherwise valid nominal type would be + // wrongly rejected, e.g. `unsafe impl Sync for Alias {}` where `type Alias = Local;`. + // Expansion also reveals genuinely problematic self types (trait objects, opaque + // types, type parameters), so those keep being rejected. See issue #157756. + let self_ty = tcx.expand_free_alias_tys(trait_ref.self_ty()); // If the impl is in the same crate as the auto-trait, almost anything // goes. diff --git a/tests/ui/lazy-type-alias/auto-trait-impls.rs b/tests/ui/lazy-type-alias/auto-trait-impls.rs new file mode 100644 index 0000000000000..e5d9c988f0d5f --- /dev/null +++ b/tests/ui/lazy-type-alias/auto-trait-impls.rs @@ -0,0 +1,53 @@ +//! Auto-trait impls whose self type is a free (lazy) type alias are checked against the +//! type the alias *expands* to, not the alias itself. An alias resolving to an otherwise +//! valid nominal type is accepted, just as if the underlying type had been written +//! directly; an alias resolving to a problematic type is still rejected. +//! +//! Regression test for . + +#![feature(lazy_type_alias, auto_traits, negative_impls)] +#![allow(incomplete_features)] + +struct Local; + +auto trait Marker {} + +// Aliases expanding to a local nominal type are accepted for both a cross-crate auto trait +// (`Sync`) and a local one (`Marker`), exactly as if `Local` had been named directly. +type ToLocal = Local; +unsafe impl Sync for ToLocal {} +impl Marker for ToLocal {} + +// Nested aliases are expanded transitively, so an alias chain ending in a local nominal type +// is accepted as well. +struct Inner; +type Mid = Inner; +type Nested = Mid; +impl Marker for Nested {} + +// A generic alias instantiated with a local nominal type is accepted. +struct Generic; +type Id = T; +impl Marker for Id {} + +// Negative auto-trait impls go through the same orphan check, so an alias to a local nominal +// type is accepted for them too. +struct Negated; +type ToNeg = Negated; +impl !Marker for ToNeg {} + +// An alias expanding to a reference is still rejected for the cross-crate auto trait. Using +// `&'static NotSync` (with `NotSync: !Sync`) avoids overlapping std's `Send for &T` impl, so +// the only error is the auto-trait nominal-type restriction. +struct NotSync; +impl !Sync for NotSync {} +type ToRef = &'static NotSync; +unsafe impl Send for ToRef {} +//~^ ERROR cross-crate traits with a default impl, like `Send`, can only be implemented for a struct/enum type, not `&'static NotSync` + +// An alias expanding to a trait object is still rejected for the local auto trait. +type ToDyn = dyn Send; +impl Marker for ToDyn {} +//~^ ERROR traits with a default impl, like `Marker`, cannot be implemented for trait object `(dyn Send + 'static)` + +fn main() {} diff --git a/tests/ui/lazy-type-alias/auto-trait-impls.stderr b/tests/ui/lazy-type-alias/auto-trait-impls.stderr new file mode 100644 index 0000000000000..f2ab6db19fb2b --- /dev/null +++ b/tests/ui/lazy-type-alias/auto-trait-impls.stderr @@ -0,0 +1,17 @@ +error[E0321]: traits with a default impl, like `Marker`, cannot be implemented for trait object `(dyn Send + 'static)` + --> $DIR/auto-trait-impls.rs:50:1 + | +LL | impl Marker for ToDyn {} + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: a trait object implements `Marker` if and only if `Marker` is one of the trait object's trait bounds + +error[E0321]: cross-crate traits with a default impl, like `Send`, can only be implemented for a struct/enum type, not `&'static NotSync` + --> $DIR/auto-trait-impls.rs:45:1 + | +LL | unsafe impl Send for ToRef {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ can't implement cross-crate trait with a default impl for non-struct/enum type + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0321`. diff --git a/tests/ui/type-alias-impl-trait/impl_for_weak_alias.rs b/tests/ui/type-alias-impl-trait/impl_for_weak_alias.rs index b50345bb637c3..57a092a682aaf 100644 --- a/tests/ui/type-alias-impl-trait/impl_for_weak_alias.rs +++ b/tests/ui/type-alias-impl-trait/impl_for_weak_alias.rs @@ -1,3 +1,8 @@ +//! The orphan check expands a free alias used as the self type of an auto-trait impl, so an +//! alias resolving to a tuple is accepted just like the tuple written directly. See #157756. + +//@ check-pass + #![feature(type_alias_impl_trait)] #![feature(auto_traits)] @@ -5,7 +10,6 @@ type Alias = (impl Sized, u8); auto trait Trait {} impl Trait for Alias {} -//~^ ERROR traits with a default impl, like `Trait`, cannot be implemented for type alias `Alias` #[define_opaque(Alias)] fn _def() -> Alias { diff --git a/tests/ui/type-alias-impl-trait/impl_for_weak_alias.stderr b/tests/ui/type-alias-impl-trait/impl_for_weak_alias.stderr deleted file mode 100644 index a13e9eaab3add..0000000000000 --- a/tests/ui/type-alias-impl-trait/impl_for_weak_alias.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0321]: traits with a default impl, like `Trait`, cannot be implemented for type alias `Alias` - --> $DIR/impl_for_weak_alias.rs:7:1 - | -LL | impl Trait for Alias {} - | ^^^^^^^^^^^^^^^^^^^^ - | - = note: a trait object implements `Trait` if and only if `Trait` is one of the trait object's trait bounds - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0321`. From 5e016b66c3b14d2934eb64d344e91c2246578ed5 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Sat, 13 Jun 2026 12:13:31 +0200 Subject: [PATCH 05/28] Stabilize `#[my_macro] mod foo;` (part of `proc_macro_hygiene`) --- compiler/rustc_expand/src/expand.rs | 40 +--------------- compiler/rustc_feature/src/unstable.rs | 2 +- .../proc-macro/attributes-on-modules-fail.rs | 27 ----------- .../attributes-on-modules-fail.stderr | 44 +---------------- .../ui/proc-macro/auxiliary/expect-modules.rs | 41 ++++++++++++++++ tests/ui/proc-macro/inner-attr-file-mod.rs | 3 +- .../ui/proc-macro/inner-attr-file-mod.stderr | 12 +---- tests/ui/proc-macro/module.rs | 5 +- tests/ui/proc-macro/modules-in-input.rs | 47 +++++++++++++++++++ tests/ui/proc-macro/unsafe-mod.rs | 2 - 10 files changed, 99 insertions(+), 124 deletions(-) create mode 100644 tests/ui/proc-macro/auxiliary/expect-modules.rs create mode 100644 tests/ui/proc-macro/modules-in-input.rs diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 0fd21e017ff61..c6bbeda36e930 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -5,7 +5,7 @@ use std::{iter, mem, slice}; use rustc_ast::mut_visit::*; use rustc_ast::tokenstream::TokenStream; -use rustc_ast::visit::{self, AssocCtxt, Visitor, VisitorResult, try_visit, walk_list}; +use rustc_ast::visit::{AssocCtxt, Visitor, VisitorResult, try_visit, walk_list}; use rustc_ast::{ self as ast, AssocItemKind, AstNodeWrapper, AttrArgs, AttrItemKind, AttrStyle, AttrVec, DUMMY_NODE_ID, DelegationSource, DelegationSuffixes, EarlyParsedAttribute, ExprKind, @@ -20,7 +20,7 @@ use rustc_attr_parsing::{ }; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_data_structures::stack::ensure_sufficient_stack; -use rustc_errors::{PResult, msg}; +use rustc_errors::PResult; use rustc_feature::Features; use rustc_hir::Target; use rustc_hir::def::MacroKinds; @@ -29,7 +29,6 @@ use rustc_parse::parser::{ AllowConstBlockItems, AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma, Recovery, token_descr, }; -use rustc_session::Session; use rustc_session::errors::feature_err; use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS}; use rustc_span::hygiene::SyntaxContext; @@ -770,7 +769,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } InvocationKind::Attr { attr, pos, mut item, derives } => { if let Some(expander) = ext.as_attr() { - self.gate_proc_macro_input(&item); self.gate_proc_macro_attr_item(span, &item); let tokens = match &item { // FIXME: Collect tokens and use them instead of generating @@ -904,9 +902,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { InvocationKind::Derive { path, item, is_const } => match ext { SyntaxExtensionKind::Derive(expander) | SyntaxExtensionKind::LegacyDerive(expander) => { - if let SyntaxExtensionKind::Derive(..) = ext { - self.gate_proc_macro_input(&item); - } // The `MetaItem` representing the trait to derive can't // have an unsafe around it (as of now). let meta = ast::MetaItem { @@ -1043,37 +1038,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { .emit(); } - fn gate_proc_macro_input(&self, annotatable: &Annotatable) { - struct GateProcMacroInput<'a> { - sess: &'a Session, - } - - impl<'ast, 'a> Visitor<'ast> for GateProcMacroInput<'a> { - fn visit_item(&mut self, item: &'ast ast::Item) { - match &item.kind { - ItemKind::Mod(_, _, mod_kind) - if !matches!(mod_kind, ModKind::Loaded(_, Inline::Yes, _)) => - { - feature_err( - self.sess, - sym::proc_macro_hygiene, - item.span, - msg!("file modules in proc macro input are unstable"), - ) - .emit(); - } - _ => {} - } - - visit::walk_item(self, item); - } - } - - if !self.cx.ecfg.features.proc_macro_hygiene() { - annotatable.visit_with(&mut GateProcMacroInput { sess: self.cx.sess }); - } - } - fn parse_ast_fragment( &mut self, toks: TokenStream, diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e1f63b7c3dce8..f1df5afd75daa 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -688,7 +688,7 @@ declare_features! ( (unstable, powerpc_target_feature, "1.27.0", Some(150255)), /// The prfchw target feature on x86. (unstable, prfchw_target_feature, "1.78.0", Some(150256)), - /// Allows macro attributes on expressions, statements and non-inline modules. + /// Allows macro attributes on expressions and statements. (unstable, proc_macro_hygiene, "1.30.0", Some(54727)), /// Allows the use of raw-dylibs on ELF platforms (incomplete, raw_dylib_elf, "1.87.0", Some(135694)), diff --git a/tests/ui/proc-macro/attributes-on-modules-fail.rs b/tests/ui/proc-macro/attributes-on-modules-fail.rs index 80701523d49e6..6ad935d494458 100644 --- a/tests/ui/proc-macro/attributes-on-modules-fail.rs +++ b/tests/ui/proc-macro/attributes-on-modules-fail.rs @@ -17,31 +17,4 @@ type A = X; //~ ERROR cannot find type `X` in this scope #[derive(Copy)] //~ ERROR `derive` may only be applied to `struct`s, `enum`s and `union`s mod n {} -#[empty_attr] -mod module; //~ ERROR file modules in proc macro input are unstable - -#[empty_attr] -mod outer { - mod inner; //~ ERROR file modules in proc macro input are unstable - - mod inner_inline {} // OK -} - -#[derive(Empty)] -struct S { - field: [u8; { - #[path = "outer/inner.rs"] - mod inner; //~ ERROR file modules in proc macro input are unstable - mod inner_inline {} // OK - 0 - }], -} - -#[identity_attr] -fn f() { - #[path = "outer/inner.rs"] - mod inner; //~ ERROR file modules in proc macro input are unstable - mod inner_inline {} // OK -} - fn main() {} diff --git a/tests/ui/proc-macro/attributes-on-modules-fail.stderr b/tests/ui/proc-macro/attributes-on-modules-fail.stderr index 1dc87fd0d5513..78593bf8de897 100644 --- a/tests/ui/proc-macro/attributes-on-modules-fail.stderr +++ b/tests/ui/proc-macro/attributes-on-modules-fail.stderr @@ -6,46 +6,6 @@ LL | #[derive(Copy)] LL | mod n {} | -------- not a `struct`, `enum` or `union` -error[E0658]: file modules in proc macro input are unstable - --> $DIR/attributes-on-modules-fail.rs:21:1 - | -LL | mod module; - | ^^^^^^^^^^^ - | - = 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[E0658]: file modules in proc macro input are unstable - --> $DIR/attributes-on-modules-fail.rs:25:5 - | -LL | mod inner; - | ^^^^^^^^^^ - | - = 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[E0658]: file modules in proc macro input are unstable - --> $DIR/attributes-on-modules-fail.rs:34:9 - | -LL | mod inner; - | ^^^^^^^^^^ - | - = 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[E0658]: file modules in proc macro input are unstable - --> $DIR/attributes-on-modules-fail.rs:43:5 - | -LL | mod inner; - | ^^^^^^^^^^ - | - = 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[E0425]: cannot find type `Y` in this scope --> $DIR/attributes-on-modules-fail.rs:11:14 | @@ -68,7 +28,7 @@ help: consider importing this struct LL + use m::X; | -error: aborting due to 7 previous errors +error: aborting due to 3 previous errors -Some errors have detailed explanations: E0425, E0658, E0774. +Some errors have detailed explanations: E0425, E0774. For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/proc-macro/auxiliary/expect-modules.rs b/tests/ui/proc-macro/auxiliary/expect-modules.rs new file mode 100644 index 0000000000000..57bd5f39f71a3 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/expect-modules.rs @@ -0,0 +1,41 @@ +/// Macros that assert that certain `mod` items are present in their input. + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn expect_mod_item(_attrs: TokenStream, item: TokenStream) -> TokenStream { + let s = item.to_string(); + + assert_eq!(s, "mod module;"); + + item +} + +#[proc_macro_attribute] +pub fn expect_mods_attr(_attrs: TokenStream, item: TokenStream) -> TokenStream { + let s = item.to_string(); + + assert_contains(&s, "mod attr_outlined;"); + assert_contains(&s, "mod attr_inline {}"); + + item +} + +#[proc_macro_derive(ExpectModsDerive)] +pub fn expect_mods_derive(item: TokenStream) -> TokenStream { + let s = item.to_string(); + + assert_contains(&s, "mod derive_outlined;"); + assert_contains(&s, "mod derive_inline {}"); + + TokenStream::new() +} + +#[track_caller] +fn assert_contains(s: &str, needle: &str) { + if !s.contains(needle) { + panic!("{needle:?} not found in:\n---\n{s}\n---\n"); + } +} diff --git a/tests/ui/proc-macro/inner-attr-file-mod.rs b/tests/ui/proc-macro/inner-attr-file-mod.rs index e592331c2c6db..535341957ade5 100644 --- a/tests/ui/proc-macro/inner-attr-file-mod.rs +++ b/tests/ui/proc-macro/inner-attr-file-mod.rs @@ -9,8 +9,7 @@ extern crate test_macros; #[deny(unused_attributes)] mod module_with_attrs; -//~^ ERROR file modules in proc macro input are unstable -//~| ERROR custom inner attributes are unstable +//~^ ERROR custom inner attributes are unstable fn main() {} diff --git a/tests/ui/proc-macro/inner-attr-file-mod.stderr b/tests/ui/proc-macro/inner-attr-file-mod.stderr index 92262284a00c4..a377313fc519e 100644 --- a/tests/ui/proc-macro/inner-attr-file-mod.stderr +++ b/tests/ui/proc-macro/inner-attr-file-mod.stderr @@ -18,16 +18,6 @@ LL | #![print_attr] = help: add `#![feature(custom_inner_attributes)]` 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[E0658]: file modules in proc macro input are unstable - --> $DIR/inner-attr-file-mod.rs:11:1 - | -LL | mod module_with_attrs; - | ^^^^^^^^^^^^^^^^^^^^^^ - | - = 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[E0658]: custom inner attributes are unstable --> $DIR/inner-attr-file-mod.rs:11:1 | @@ -38,6 +28,6 @@ LL | mod module_with_attrs; = help: add `#![feature(custom_inner_attributes)]` 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 4 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/proc-macro/module.rs b/tests/ui/proc-macro/module.rs index 5878f1b7ddd8a..f01bd5e50f0ab 100644 --- a/tests/ui/proc-macro/module.rs +++ b/tests/ui/proc-macro/module.rs @@ -1 +1,4 @@ -//@ ignore-auxiliary (used by `./attributes-on-modules-fail.rs`) +//@ ignore-auxiliary (used by `./attributes-on-modules-fail.rs` and `modules-in-input.rs`) + +// An item that can be referenced to verify the module has been properly loaded. +pub const fn hello() {} diff --git a/tests/ui/proc-macro/modules-in-input.rs b/tests/ui/proc-macro/modules-in-input.rs new file mode 100644 index 0000000000000..50b4dc9e40f44 --- /dev/null +++ b/tests/ui/proc-macro/modules-in-input.rs @@ -0,0 +1,47 @@ +//@ check-pass +//@ proc-macro: expect-modules.rs +//@ reference: macro.invocation.attr.mod + +// Verifies how module items are represented in proc macro input. + +#[macro_use] +extern crate expect_modules; + +#[expect_modules::expect_mod_item] +mod module; + +fn check() { + module::hello(); +} + +#[expect_modules::expect_mods_attr] +mod outer { + #[path = "../module.rs"] + mod attr_outlined; + mod attr_inline {} + + fn check() { + attr_outlined::hello(); + } +} + +#[expect_modules::expect_mods_attr] +fn foo() { + #[path = "module.rs"] + mod attr_outlined; + mod attr_inline {} + attr_outlined::hello(); +} + +#[derive(expect_modules::ExpectModsDerive)] +struct Foo( + [u8; { + #[path = "module.rs"] + mod derive_outlined; + mod derive_inline {} + derive_outlined::hello(); + 0 + }], +); + +fn main() {} diff --git a/tests/ui/proc-macro/unsafe-mod.rs b/tests/ui/proc-macro/unsafe-mod.rs index f8453c2f62cd4..d9fbe7a8c5e1d 100644 --- a/tests/ui/proc-macro/unsafe-mod.rs +++ b/tests/ui/proc-macro/unsafe-mod.rs @@ -1,8 +1,6 @@ //@ run-pass //@ proc-macro: macro-only-syntax.rs -#![feature(proc_macro_hygiene)] - extern crate macro_only_syntax; #[macro_only_syntax::expect_unsafe_mod] From 5b4299fa7c18f5ecad5bf0c7a793bf843ee550a4 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sun, 28 Jun 2026 16:15:44 +0000 Subject: [PATCH 06/28] Implement `IntoIterator` for `[&[mut]] Box<[T; N], A>` --- library/alloc/src/boxed.rs | 2 + library/alloc/src/boxed/iter.rs | 158 +++++++++++++++++- library/alloc/src/vec/into_iter.rs | 2 +- library/core/src/array/iter.rs | 5 +- .../ui/iterators/into-iter-on-arrays-2018.rs | 5 +- .../iterators/into-iter-on-arrays-2018.stderr | 19 +-- .../ui/iterators/into-iter-on-arrays-2021.rs | 3 +- .../iterators/into-iter-on-arrays-lint.fixed | 32 +--- .../ui/iterators/into-iter-on-arrays-lint.rs | 16 -- .../iterators/into-iter-on-arrays-lint.stderr | 74 +------- 10 files changed, 180 insertions(+), 136 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 3087a7d2004bc..760379b925fd8 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -218,6 +218,8 @@ mod iter; /// [`ThinBox`] implementation. mod thin; +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +pub use iter::BoxedArrayIntoIter; #[unstable(feature = "thin_box", issue = "92791")] pub use thin::ThinBox; diff --git a/library/alloc/src/boxed/iter.rs b/library/alloc/src/boxed/iter.rs index 90582aa49c6d7..0e6afcf062354 100644 --- a/library/alloc/src/boxed/iter.rs +++ b/library/alloc/src/boxed/iter.rs @@ -1,18 +1,19 @@ use core::async_iter::AsyncIterator; -use core::iter::FusedIterator; +use core::iter::{FusedIterator, TrustedLen}; +use core::num::NonZero; use core::pin::Pin; use core::slice; use core::task::{Context, Poll}; -use crate::alloc::Allocator; +use crate::alloc::{Allocator, Global}; #[cfg(not(no_global_oom_handling))] use crate::borrow::Cow; use crate::boxed::Box; #[cfg(not(no_global_oom_handling))] use crate::string::String; -use crate::vec; #[cfg(not(no_global_oom_handling))] use crate::vec::Vec; +use crate::{fmt, vec}; #[stable(feature = "rust1", since = "1.0.0")] impl Iterator for Box { @@ -192,3 +193,154 @@ impl<'a> FromIterator> for Box { String::from_iter(iter).into_boxed_str() } } + +/// This implementation is required to make sure that the `Box<[I; N]>: IntoIterator` +/// implementation doesn't overlap with `IntoIterator for T where T: Iterator` blanket. +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl !Iterator for Box<[I; N], A> {} + +/// This implementation is required to make sure that the `&Box<[I; N]>: IntoIterator` +/// implementation doesn't overlap with `IntoIterator for T where T: Iterator` blanket. +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl<'a, const N: usize, I, A: Allocator> !Iterator for &'a Box<[I; N], A> {} + +/// This implementation is required to make sure that the `&mut Box<[I; N]>: IntoIterator` +/// implementation doesn't overlap with `IntoIterator for T where T: Iterator` blanket. +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl<'a, const N: usize, I, A: Allocator> !Iterator for &'a mut Box<[I; N], A> {} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl<'a, T, const N: usize, A: Allocator> IntoIterator for &'a Box<[T; N], A> { + type IntoIter = slice::Iter<'a, T>; + type Item = &'a T; + fn into_iter(self) -> slice::Iter<'a, T> { + self.iter() + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl<'a, T, const N: usize, A: Allocator> IntoIterator for &'a mut Box<[T; N], A> { + type IntoIter = slice::IterMut<'a, T>; + type Item = &'a mut T; + fn into_iter(self) -> slice::IterMut<'a, T> { + self.iter_mut() + } +} + +/// A by-value `Box<[T; N]>` iterator. +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +#[rustc_insignificant_dtor] +pub struct BoxedArrayIntoIter { + // FIXME: make a more efficient implementation (without the need to store capacity) + inner: vec::IntoIter, +} + +impl BoxedArrayIntoIter { + /// Returns an immutable slice of all elements that have not been yielded + /// yet. + #[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] + pub fn as_slice(&self) -> &[T] { + self.inner.as_slice() + } + + /// Returns a mutable slice of all elements that have not been yielded yet. + #[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] + pub fn as_mut_slice(&mut self) -> &mut [T] { + self.inner.as_mut_slice() + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl Iterator for BoxedArrayIntoIter { + type Item = T; + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } + + #[inline] + fn fold(self, init: Acc, fold: Fold) -> Acc + where + Fold: FnMut(Acc, Self::Item) -> Acc, + { + self.inner.fold(init, fold) + } + + fn count(self) -> usize { + self.len() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn advance_by(&mut self, n: usize) -> Result<(), NonZero> { + self.inner.advance_by(n) + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl DoubleEndedIterator for BoxedArrayIntoIter { + fn next_back(&mut self) -> Option { + self.inner.next_back() + } + + #[inline] + fn rfold(self, init: Acc, rfold: Fold) -> Acc + where + Fold: FnMut(Acc, Self::Item) -> Acc, + { + self.inner.rfold(init, rfold) + } + + fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { + self.inner.advance_back_by(n) + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl ExactSizeIterator for BoxedArrayIntoIter { + fn len(&self) -> usize { + self.inner.len() + } + + fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl FusedIterator for BoxedArrayIntoIter {} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +unsafe impl TrustedLen for BoxedArrayIntoIter {} + +#[cfg(not(no_global_oom_handling))] +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl Clone for BoxedArrayIntoIter { + fn clone(&self) -> Self { + Self { inner: self.inner.clone() } + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl fmt::Debug for BoxedArrayIntoIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Only print the elements that were not yielded yet: we cannot + // access the yielded elements anymore. + f.debug_tuple("BoxedArrayIntoIter").field(&self.as_slice()).finish() + } +} + +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl IntoIterator for Box<[T; N], A> { + type IntoIter = BoxedArrayIntoIter; + type Item = T; + fn into_iter(self) -> BoxedArrayIntoIter { + BoxedArrayIntoIter { inner: (self as Box<[T], A>).into_vec().into_iter() } + } +} diff --git a/library/alloc/src/vec/into_iter.rs b/library/alloc/src/vec/into_iter.rs index 97e4912596b94..06e40ace01c82 100644 --- a/library/alloc/src/vec/into_iter.rs +++ b/library/alloc/src/vec/into_iter.rs @@ -511,7 +511,7 @@ where #[doc(hidden)] #[unstable(issue = "none", feature = "std_internals")] #[rustc_unsafe_specialization_marker] -pub trait NonDrop {} +trait NonDrop {} // T: Copy as approximation for !Drop since get_unchecked does not advance self.ptr // and thus we can't implement drop-handling diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs index 7aed4a1324060..0877b7bad9512 100644 --- a/library/core/src/array/iter.rs +++ b/library/core/src/array/iter.rs @@ -34,6 +34,9 @@ impl IntoIter { } } +#[stable(feature = "boxed_array_value_iter", since = "CURRENT_RUSTC_VERSION")] +impl !Iterator for [T; N] {} + // Note: the `#[rustc_skip_during_method_dispatch(array)]` on `trait IntoIterator` // hides this implementation from explicit `.into_iter()` calls on editions < 2021, // so those calls will still resolve to the slice implementation, by reference. @@ -365,7 +368,7 @@ unsafe impl TrustedLen for IntoIter {} #[doc(hidden)] #[unstable(issue = "none", feature = "std_internals")] #[rustc_unsafe_specialization_marker] -pub trait NonDrop {} +trait NonDrop {} // T: Copy as approximation for !Drop since get_unchecked does not advance self.alive // and thus we can't implement drop-handling diff --git a/tests/ui/iterators/into-iter-on-arrays-2018.rs b/tests/ui/iterators/into-iter-on-arrays-2018.rs index d23544052362f..2999d085c8738 100644 --- a/tests/ui/iterators/into-iter-on-arrays-2018.rs +++ b/tests/ui/iterators/into-iter-on-arrays-2018.rs @@ -5,6 +5,7 @@ use std::array::IntoIter; use std::ops::Deref; use std::rc::Rc; use std::slice::Iter; +use std::boxed::BoxedArrayIntoIter; fn main() { let array = [0; 10]; @@ -15,9 +16,7 @@ fn main() { //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` //~| WARNING this changes meaning - let _: Iter<'_, i32> = Box::new(array).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning + let _: BoxedArrayIntoIter = Box::new(array).into_iter(); let _: Iter<'_, i32> = Rc::new(array).into_iter(); //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` diff --git a/tests/ui/iterators/into-iter-on-arrays-2018.stderr b/tests/ui/iterators/into-iter-on-arrays-2018.stderr index 6419d779b4f57..769ea451a4f14 100644 --- a/tests/ui/iterators/into-iter-on-arrays-2018.stderr +++ b/tests/ui/iterators/into-iter-on-arrays-2018.stderr @@ -1,5 +1,5 @@ warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-2018.rs:14:34 + --> $DIR/into-iter-on-arrays-2018.rs:15:34 | LL | let _: Iter<'_, i32> = array.into_iter(); | ^^^^^^^^^ @@ -19,16 +19,7 @@ LL + let _: Iter<'_, i32> = IntoIterator::into_iter(array); | warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-2018.rs:18:44 - | -LL | let _: Iter<'_, i32> = Box::new(array).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-2018.rs:22:43 + --> $DIR/into-iter-on-arrays-2018.rs:21:43 | LL | let _: Iter<'_, i32> = Rc::new(array).into_iter(); | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` @@ -37,7 +28,7 @@ LL | let _: Iter<'_, i32> = Rc::new(array).into_iter(); = note: for more information, see warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-2018.rs:25:41 + --> $DIR/into-iter-on-arrays-2018.rs:24:41 | LL | let _: Iter<'_, i32> = Array(array).into_iter(); | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` @@ -46,7 +37,7 @@ LL | let _: Iter<'_, i32> = Array(array).into_iter(); = note: for more information, see warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-2018.rs:32:24 + --> $DIR/into-iter-on-arrays-2018.rs:31:24 | LL | for _ in [1, 2, 3].into_iter() {} | ^^^^^^^^^ @@ -64,5 +55,5 @@ LL - for _ in [1, 2, 3].into_iter() {} LL + for _ in [1, 2, 3] {} | -warning: 5 warnings emitted +warning: 4 warnings emitted diff --git a/tests/ui/iterators/into-iter-on-arrays-2021.rs b/tests/ui/iterators/into-iter-on-arrays-2021.rs index 1bda0ebf6cb8e..918cc4cf0801f 100644 --- a/tests/ui/iterators/into-iter-on-arrays-2021.rs +++ b/tests/ui/iterators/into-iter-on-arrays-2021.rs @@ -4,13 +4,14 @@ use std::array::IntoIter; use std::ops::Deref; use std::rc::Rc; +use std::boxed::BoxedArrayIntoIter; fn main() { let array = [0; 10]; // In 2021, the method dispatches to `IntoIterator for [T; N]`. let _: IntoIter = array.into_iter(); - let _: IntoIter = Box::new(array).into_iter(); + let _: BoxedArrayIntoIter = Box::new(array).into_iter(); // The `array_into_iter` lint doesn't cover other wrappers that deref to an array. let _: IntoIter = Rc::new(array).into_iter(); diff --git a/tests/ui/iterators/into-iter-on-arrays-lint.fixed b/tests/ui/iterators/into-iter-on-arrays-lint.fixed index 848b13750d77a..0c105a55871b4 100644 --- a/tests/ui/iterators/into-iter-on-arrays-lint.fixed +++ b/tests/ui/iterators/into-iter-on-arrays-lint.fixed @@ -22,31 +22,15 @@ fn main() { //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` //~| WARNING this changes meaning - Box::new(small).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning - Box::new([1, 2]).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning - Box::new(big).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning - Box::new([0u8; 33]).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning + Box::new(small).into_iter(); + Box::new([1, 2]).into_iter(); + Box::new(big).into_iter(); + Box::new([0u8; 33]).into_iter(); - Box::new(Box::new(small)).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning - Box::new(Box::new([1, 2])).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning - Box::new(Box::new(big)).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning - Box::new(Box::new([0u8; 33])).iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning + Box::new(Box::new(small)).into_iter(); + Box::new(Box::new([1, 2])).into_iter(); + Box::new(Box::new(big)).into_iter(); + Box::new(Box::new([0u8; 33])).into_iter(); // Expressions that should not (&[1, 2]).into_iter(); diff --git a/tests/ui/iterators/into-iter-on-arrays-lint.rs b/tests/ui/iterators/into-iter-on-arrays-lint.rs index a505ba8313f56..6b9d840f138fd 100644 --- a/tests/ui/iterators/into-iter-on-arrays-lint.rs +++ b/tests/ui/iterators/into-iter-on-arrays-lint.rs @@ -23,30 +23,14 @@ fn main() { //~| WARNING this changes meaning Box::new(small).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new([1, 2]).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new(big).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new([0u8; 33]).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new(Box::new(small)).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new(Box::new([1, 2])).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new(Box::new(big)).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning Box::new(Box::new([0u8; 33])).into_iter(); - //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` - //~| WARNING this changes meaning // Expressions that should not (&[1, 2]).into_iter(); diff --git a/tests/ui/iterators/into-iter-on-arrays-lint.stderr b/tests/ui/iterators/into-iter-on-arrays-lint.stderr index b173eb01d22ec..44375d20717b8 100644 --- a/tests/ui/iterators/into-iter-on-arrays-lint.stderr +++ b/tests/ui/iterators/into-iter-on-arrays-lint.stderr @@ -75,77 +75,5 @@ LL - [0u8; 33].into_iter(); LL + IntoIterator::into_iter([0u8; 33]); | -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:25:21 - | -LL | Box::new(small).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:28:22 - | -LL | Box::new([1, 2]).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:31:19 - | -LL | Box::new(big).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:34:25 - | -LL | Box::new([0u8; 33]).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:38:31 - | -LL | Box::new(Box::new(small)).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:41:32 - | -LL | Box::new(Box::new([1, 2])).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:44:29 - | -LL | Box::new(Box::new(big)).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> $DIR/into-iter-on-arrays-lint.rs:47:35 - | -LL | Box::new(Box::new([0u8; 33])).into_iter(); - | ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter` - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see - -warning: 12 warnings emitted +warning: 4 warnings emitted From 69d7670b2d078667779954ab5ddbded47a6b8136 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 24 Jun 2026 21:29:11 +0200 Subject: [PATCH 07/28] add `-Zforce-intrinsic-fallback` flag --- .../rustc_codegen_ssa/src/mir/intrinsic.rs | 11 ++++++ compiler/rustc_monomorphize/src/collector.rs | 11 +++++- compiler/rustc_session/src/options.rs | 3 ++ .../force-intrinsic-fallback.md | 6 +++ .../codegen-llvm/force-intrinsic-fallback.rs | 38 +++++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md create mode 100644 tests/codegen-llvm/force-intrinsic-fallback.rs diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index 99546bea65959..413cb5f2c860a 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -63,6 +63,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { result_place: Option>, source_info: SourceInfo, ) -> IntrinsicResult<'tcx, Bx::Value> { + // When `-Zforce-intrinsic-fallback` is enabled, always use the fallback body if it exists, + if bx.tcx().sess.opts.unstable_opts.force_intrinsic_fallback + && let Some(def) = bx.tcx().intrinsic(instance.def_id()) + && !def.must_be_overridden + { + return IntrinsicResult::Fallback(ty::Instance::new_raw( + instance.def_id(), + instance.args, + )); + } + let span = source_info.span; let name = bx.tcx().item_name(instance.def_id()); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index ceb044c102f69..bd608583d818c 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -995,11 +995,18 @@ fn visit_instance_use<'tcx>( output.push(create_fn_mono_item(tcx, panic_instance, source)); } } else if !intrinsic.must_be_overridden - && !tcx.sess.replaced_intrinsics.contains(&intrinsic.name) + && (tcx.sess.opts.unstable_opts.force_intrinsic_fallback + || !tcx.sess.replaced_intrinsics.contains(&intrinsic.name)) { // Codegen the fallback body of intrinsics with fallback bodies. // We have to skip this otherwise as there's no body to codegen. - // We also skip intrinsics the backend handles, to reduce monomorphizations. + // + // We also skip `replaced_intrinsics` which are always replaced by the backend and hence + // monomorphizing the fallback body would be pointless. + // + // However, when -Zforce-intrinsic-fallback is set (e.g. to test the fallback + // implementations) we ignore the optimization hint and do monomorphize + // the fallback body. let instance = ty::Instance::new_raw(instance.def_id(), instance.args); if tcx.should_codegen_locally(instance) { output.push(create_fn_mono_item(tcx, instance, source)); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index efc8a70f4feb2..d4b9f9a795064 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2413,6 +2413,9 @@ options! { fmt_debug: FmtDebug = (FmtDebug::Full, parse_fmt_debug, [TRACKED], "how detailed `#[derive(Debug)]` should be. `full` prints types recursively, \ `shallow` prints only type names, `none` prints nothing and disables `{:?}`. (default: `full`)"), + force_intrinsic_fallback: bool = (false, parse_bool, [TRACKED], + "always use the fallback body of an intrinsic, if it has one, instead of lowering \ + the intrinsic in the codegen backend (default: no)."), force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], "force all crates to be `rustc_private` unstable (default: no)"), function_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED], diff --git a/src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md b/src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md new file mode 100644 index 0000000000000..41f6ae3237908 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/force-intrinsic-fallback.md @@ -0,0 +1,6 @@ +## `force-intrinsic-fallback` + +Configures codegen to always use the fallback body of an intrinsic, if it has one, +instead of lowering the intrinsic in the codegen backend. + +This is useful for testing the fallback implementation. diff --git a/tests/codegen-llvm/force-intrinsic-fallback.rs b/tests/codegen-llvm/force-intrinsic-fallback.rs new file mode 100644 index 0000000000000..4719745ed673a --- /dev/null +++ b/tests/codegen-llvm/force-intrinsic-fallback.rs @@ -0,0 +1,38 @@ +//@ compile-flags: --crate-type=lib -C no-prepopulate-passes -Copt-level=3 +// +//@ revisions: NORMAL FALLBACK +//@ [FALLBACK] compile-flags: -Zforce-intrinsic-fallback +#![feature(core_intrinsics, funnel_shifts)] + +// Check the effect of `-Zforce-intrinsic-fallback`. +// +// Without the flag, the dedicated backend lowering of the intrinsic is used. +// With the flag, the fallback body is called instead. + +#[no_mangle] +pub fn call_minimumf32(x: f32, y: f32) -> f32 { + // CHECK-LABEL: @call_minimumf32 + + // NORMAL: call float @llvm.minimum.f32 + // NORMAL-NOT: minimumf32 + + // FALLBACK-NOT: @llvm.minimum + // FALLBACK: call noundef float @{{.*}}minimumf32 + core::intrinsics::minimumf32(x, y) +} + +// Codegen backends can return a list of `replaced_intrinsics`, for which codegen of the fallback is +// normally skipped. `unchecked_funnel_shl` is in that list for the LLVM backend, so we test it here +// to ensure that with the flag enabled the fallback body is actually code generated and called. + +#[no_mangle] +pub fn call_funnel_shl(a: u32, b: u32, shift: u32) -> u32 { + // CHECK-LABEL: @call_funnel_shl + + // NORMAL: call i32 @llvm.fshl.i32 + // NORMAL-NOT: funnel_shl + + // FALLBACK-NOT: @llvm.fshl + // FALLBACK: call noundef i32 @{{.*}}funnel_shl + unsafe { core::intrinsics::unchecked_funnel_shl(a, b, shift) } +} From cdac740e3311d51419975e7edbab2aef1c41c388 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 12 May 2026 21:20:35 +1000 Subject: [PATCH 08/28] Move `std::io::IoHandle` to `core::io` --- library/alloc/src/io/mod.rs | 2 +- library/core/src/io/mod.rs | 20 ++++++++++++++++++++ library/std/src/fs.rs | 2 ++ library/std/src/io/mod.rs | 16 +--------------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 58e5ffa39d0c4..8a26543d1be09 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -17,4 +17,4 @@ pub use core::io::{ }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use core::io::{OsFunctions, chain, take}; +pub use core::io::{IoHandle, OsFunctions, chain, take}; diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index b3c8815cb062c..c76ba844100fd 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -27,3 +27,23 @@ pub use self::{ error::{Custom, CustomOwner, OsFunctions}, util::{chain, take}, }; + +/// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically +/// implemented for handle types like [`Arc`][arc] as well. +/// +/// This trait should only be implemented for types where `<&T as Trait>::method(&mut &value, ..)` +/// would be identical to `::method(&mut value, ..)`. +/// +/// [`File`][file] passes this test, as operations on `&File` and `File` both affect +/// the same underlying file. +/// `[u8]` fails, because any modification to `&mut &[u8]` would only affect a temporary +/// and be lost after the method has been called. +/// +// FIXME(#74481): Hard-links required to link from `core` to `std` +/// [file]: ../../std/fs/struct.File.html +/// [arc]: ../../alloc/sync/struct.Arc.html +/// [`Write`]: ../../std/io/trait.Write.html +/// [`Seek`]: ../../std/io/trait.Seek.html +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub trait IoHandle {} diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 750ddb91c482b..77be07b097b03 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1540,6 +1540,8 @@ impl Seek for File { (&*self).stream_position() } } +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] impl crate::io::IoHandle for File {} impl Dir { diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index dc623a3e82c2c..6251fa3918fd9 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -299,6 +299,7 @@ mod tests; use core::slice::memchr; +pub(crate) use alloc_crate::io::IoHandle; use alloc_crate::io::OsFunctions; #[unstable(feature = "raw_os_error_ty", issue = "107792")] pub use alloc_crate::io::RawOsError; @@ -1980,21 +1981,6 @@ pub enum SeekFrom { Current(#[stable(feature = "rust1", since = "1.0.0")] i64), } -/// Marks that a type `T` can have IO traits such as [`Seek`], [`Write`], etc. automatically -/// implemented for handle types like [`Arc`][arc] as well. -/// -/// This trait should only be implemented for types where `<&T as Trait>::method(&mut &value, ..)` -/// would be identical to `::method(&mut value, ..)`. -/// -/// [`File`][file] passes this test, as operations on `&File` and `File` both affect -/// the same underlying file. -/// `[u8]` fails, because any modification to `&mut &[u8]` would only affect a temporary -/// and be lost after the method has been called. -/// -/// [file]: crate::fs::File -/// [arc]: crate::sync::Arc -pub(crate) trait IoHandle {} - fn read_until(r: &mut R, delim: u8, buf: &mut Vec) -> Result { let mut read = 0; loop { From 4746813a2ff73bb785c3121418be65baf2243d18 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 12 May 2026 21:50:01 +1000 Subject: [PATCH 09/28] Move `std::io::SizeHint` to `core::io` --- library/alloc/src/io/impls.rs | 22 ++++++ library/alloc/src/io/mod.rs | 3 +- library/core/src/io/impls.rs | 35 +++++++++ library/core/src/io/mod.rs | 3 + library/core/src/io/size_hint.rs | 25 +++++++ library/core/src/io/util.rs | 60 +++++++++++++++- library/std/src/io/buffered/bufreader.rs | 2 + library/std/src/io/mod.rs | 90 +----------------------- library/std/src/io/util.rs | 21 +----- 9 files changed, 150 insertions(+), 111 deletions(-) create mode 100644 library/alloc/src/io/impls.rs create mode 100644 library/core/src/io/impls.rs create mode 100644 library/core/src/io/size_hint.rs diff --git a/library/alloc/src/io/impls.rs b/library/alloc/src/io/impls.rs new file mode 100644 index 0000000000000..6f392bba93ef4 --- /dev/null +++ b/library/alloc/src/io/impls.rs @@ -0,0 +1,22 @@ +use crate::boxed::Box; +use crate::io::SizeHint; + +// ============================================================================= +// Forwarding implementations + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for Box { + #[inline] + fn lower_bound(&self) -> usize { + SizeHint::lower_bound(&**self) + } + + #[inline] + fn upper_bound(&self) -> Option { + SizeHint::upper_bound(&**self) + } +} + +// ============================================================================= +// In-memory buffer implementations diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 8a26543d1be09..86dc8c685a985 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -1,6 +1,7 @@ //! Traits, helpers, and type definitions for core I/O functionality. mod error; +mod impls; #[unstable(feature = "raw_os_error_ty", issue = "107792")] pub use core::io::RawOsError; @@ -17,4 +18,4 @@ pub use core::io::{ }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use core::io::{IoHandle, OsFunctions, chain, take}; +pub use core::io::{IoHandle, OsFunctions, SizeHint, chain, take}; diff --git a/library/core/src/io/impls.rs b/library/core/src/io/impls.rs new file mode 100644 index 0000000000000..cc222c56121e2 --- /dev/null +++ b/library/core/src/io/impls.rs @@ -0,0 +1,35 @@ +use crate::io::SizeHint; + +// ============================================================================= +// Forwarding implementations + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for &mut T { + #[inline] + fn lower_bound(&self) -> usize { + SizeHint::lower_bound(*self) + } + + #[inline] + fn upper_bound(&self) -> Option { + SizeHint::upper_bound(*self) + } +} + +// ============================================================================= +// In-memory buffer implementations + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for &[u8] { + #[inline] + fn lower_bound(&self) -> usize { + self.len() + } + + #[inline] + fn upper_bound(&self) -> Option { + Some(self.len()) + } +} diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index c76ba844100fd..95fcc2d6dc19b 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -3,7 +3,9 @@ mod borrowed_buf; mod cursor; mod error; +mod impls; mod io_slice; +mod size_hint; mod util; #[unstable(feature = "core_io_borrowed_buf", issue = "117693")] @@ -25,6 +27,7 @@ pub use self::{ #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ error::{Custom, CustomOwner, OsFunctions}, + size_hint::SizeHint, util::{chain, take}, }; diff --git a/library/core/src/io/size_hint.rs b/library/core/src/io/size_hint.rs new file mode 100644 index 0000000000000..8a5b0a63c075b --- /dev/null +++ b/library/core/src/io/size_hint.rs @@ -0,0 +1,25 @@ +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +pub trait SizeHint { + fn lower_bound(&self) -> usize; + + fn upper_bound(&self) -> Option; + + fn size_hint(&self) -> (usize, Option) { + (self.lower_bound(), self.upper_bound()) + } +} + +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for T { + #[inline] + default fn lower_bound(&self) -> usize { + 0 + } + + #[inline] + default fn upper_bound(&self) -> Option { + None + } +} diff --git a/library/core/src/io/util.rs b/library/core/src/io/util.rs index 232e388433fb0..a40318bb85e08 100644 --- a/library/core/src/io/util.rs +++ b/library/core/src/io/util.rs @@ -1,4 +1,5 @@ -use crate::fmt; +use crate::io::SizeHint; +use crate::{cmp, fmt}; /// `Empty` ignores any data written via [`Write`], and will always be empty /// (returning zero bytes) when read via [`Read`]. @@ -13,6 +14,15 @@ use crate::fmt; #[derive(Copy, Clone, Debug, Default)] pub struct Empty; +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for Empty { + #[inline] + fn upper_bound(&self) -> Option { + Some(0) + } +} + /// Creates a value that is always at EOF for reads, and ignores all data written. /// /// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] @@ -63,6 +73,20 @@ pub struct Repeat { pub byte: u8, } +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for Repeat { + #[inline] + fn lower_bound(&self) -> usize { + usize::MAX + } + + #[inline] + fn upper_bound(&self) -> Option { + None + } +} + /// Creates an instance of a reader that infinitely repeats one byte. /// /// All reads from this reader will succeed by filling the specified buffer with @@ -145,6 +169,23 @@ pub struct Chain { pub done_first: bool, } +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for Chain { + #[inline] + fn lower_bound(&self) -> usize { + SizeHint::lower_bound(&self.first) + SizeHint::lower_bound(&self.second) + } + + #[inline] + fn upper_bound(&self) -> Option { + match (SizeHint::upper_bound(&self.first), SizeHint::upper_bound(&self.second)) { + (Some(first), Some(second)) => first.checked_add(second), + _ => None, + } + } +} + impl Chain { /// Consumes the `Chain`, returning the wrapped readers. /// @@ -253,6 +294,23 @@ pub struct Take { pub limit: u64, } +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +impl SizeHint for Take { + #[inline] + fn lower_bound(&self) -> usize { + cmp::min(SizeHint::lower_bound(&self.inner) as u64, self.limit) as usize + } + + #[inline] + fn upper_bound(&self) -> Option { + match SizeHint::upper_bound(&self.inner) { + Some(upper_bound) => Some(cmp::min(upper_bound as u64, self.limit) as usize), + None => self.limit.try_into().ok(), + } + } +} + impl Take { /// Returns the number of bytes that can be read before this instance will /// return EOF. diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs index 4d9eecb0d9648..075ea44377c78 100644 --- a/library/std/src/io/buffered/bufreader.rs +++ b/library/std/src/io/buffered/bufreader.rs @@ -580,6 +580,8 @@ impl Seek for BufReader { } } +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] impl SizeHint for BufReader { #[inline] fn lower_bound(&self) -> usize { diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 6251fa3918fd9..b06ec9af7239d 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -300,7 +300,6 @@ mod tests; use core::slice::memchr; pub(crate) use alloc_crate::io::IoHandle; -use alloc_crate::io::OsFunctions; #[unstable(feature = "raw_os_error_ty", issue = "107792")] pub use alloc_crate::io::RawOsError; #[doc(hidden)] @@ -316,6 +315,7 @@ pub use alloc_crate::io::{ }; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; +use alloc_crate::io::{OsFunctions, SizeHint}; #[stable(feature = "bufwriter_into_parts", since = "1.56.0")] pub use self::buffered::WriterPanicked; @@ -2538,21 +2538,6 @@ impl BufRead for Chain { // split between the two parts of the chain } -impl SizeHint for Chain { - #[inline] - fn lower_bound(&self) -> usize { - SizeHint::lower_bound(&self.first) + SizeHint::lower_bound(&self.second) - } - - #[inline] - fn upper_bound(&self) -> Option { - match (SizeHint::upper_bound(&self.first), SizeHint::upper_bound(&self.second)) { - (Some(first), Some(second)) => first.checked_add(second), - _ => None, - } - } -} - #[stable(feature = "rust1", since = "1.0.0")] impl Read for Take { fn read(&mut self, buf: &mut [u8]) -> Result { @@ -2647,21 +2632,6 @@ impl BufRead for Take { } } -impl SizeHint for Take { - #[inline] - fn lower_bound(&self) -> usize { - cmp::min(SizeHint::lower_bound(&self.inner) as u64, self.limit) as usize - } - - #[inline] - fn upper_bound(&self) -> Option { - match SizeHint::upper_bound(&self.inner) { - Some(upper_bound) => Some(cmp::min(upper_bound as u64, self.limit) as usize), - None => self.limit.try_into().ok(), - } - } -} - #[stable(feature = "seek_io_take", since = "1.89.0")] impl Seek for Take { fn seek(&mut self, pos: SeekFrom) -> Result { @@ -2770,64 +2740,6 @@ fn uninlined_slow_read_byte(reader: &mut R) -> Option> { inlined_slow_read_byte(reader) } -trait SizeHint { - fn lower_bound(&self) -> usize; - - fn upper_bound(&self) -> Option; - - fn size_hint(&self) -> (usize, Option) { - (self.lower_bound(), self.upper_bound()) - } -} - -impl SizeHint for T { - #[inline] - default fn lower_bound(&self) -> usize { - 0 - } - - #[inline] - default fn upper_bound(&self) -> Option { - None - } -} - -impl SizeHint for &mut T { - #[inline] - fn lower_bound(&self) -> usize { - SizeHint::lower_bound(*self) - } - - #[inline] - fn upper_bound(&self) -> Option { - SizeHint::upper_bound(*self) - } -} - -impl SizeHint for Box { - #[inline] - fn lower_bound(&self) -> usize { - SizeHint::lower_bound(&**self) - } - - #[inline] - fn upper_bound(&self) -> Option { - SizeHint::upper_bound(&**self) - } -} - -impl SizeHint for &[u8] { - #[inline] - fn lower_bound(&self) -> usize { - self.len() - } - - #[inline] - fn upper_bound(&self) -> Option { - Some(self.len()) - } -} - /// An iterator over the contents of an instance of `BufRead` split on a /// particular byte. /// diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs index dbda72a43a637..fbb69a11c6b92 100644 --- a/library/std/src/io/util.rs +++ b/library/std/src/io/util.rs @@ -6,7 +6,7 @@ mod tests; use crate::fmt; use crate::io::{ self, BorrowedCursor, BufRead, Empty, IoSlice, IoSliceMut, Read, Repeat, Seek, SeekFrom, Sink, - SizeHint, Write, + Write, }; #[stable(feature = "rust1", since = "1.0.0")] @@ -102,13 +102,6 @@ impl Seek for Empty { } } -impl SizeHint for Empty { - #[inline] - fn upper_bound(&self) -> Option { - Some(0) - } -} - #[stable(feature = "empty_write", since = "1.73.0")] impl Write for Empty { #[inline] @@ -240,18 +233,6 @@ impl Read for Repeat { } } -impl SizeHint for Repeat { - #[inline] - fn lower_bound(&self) -> usize { - usize::MAX - } - - #[inline] - fn upper_bound(&self) -> Option { - None - } -} - #[stable(feature = "rust1", since = "1.0.0")] impl Write for Sink { #[inline] From 9b443be80c711ba6f69a16bf6fad3a3eab08da30 Mon Sep 17 00:00:00 2001 From: jyn Date: Mon, 29 Jun 2026 09:43:54 +0200 Subject: [PATCH 10/28] tidy: make it more clear that it has to be run under bootstrap, not as a standalone command - error if RUSTC isn't set, which means it'll fall back to the user-installed default (wrong) - be more specific in `--bless` suggestions that they should use `x test tidy`, not tidy directly. --- src/tools/tidy/src/deps.rs | 3 +++ src/tools/tidy/src/extra_checks/mod.rs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 2b12e653f3748..6e34357794acc 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -683,6 +683,9 @@ pub fn check(root: &Path, cargo: &Path, tidy_ctx: TidyCtx) { /// Ensure the list of proc-macro crate transitive dependencies is up to date fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut RunningCheck) { + if std::env::var("RUSTC").is_err() { + panic!("tidy must be run under bootstrap (./x test tidy), not as a standalone command"); + } let mut cmd = cargo_metadata::MetadataCommand::new(); cmd.cargo_path(cargo) .manifest_path(root.join("Cargo.toml")) diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs index 008cc0322534a..0ee5b3534dd91 100644 --- a/src/tools/tidy/src/extra_checks/mod.rs +++ b/src/tools/tidy/src/extra_checks/mod.rs @@ -200,7 +200,9 @@ fn js_prepare(root_path: &Path, outdir: &Path, npm: &Path, tidy_ctx: &TidyCtx) - fn show_bless_help(mode: &str, action: &str, bless: bool) { if !bless { - eprintln!("rerun tidy with `--extra-checks={mode} --bless` to {action}"); + eprintln!( + "rerun with `--bless` to {action}: `./x.py test tidy --extra-checks={mode} --bless`" + ); } } From 264bf6d3048831540e314f5f4228501e34aace37 Mon Sep 17 00:00:00 2001 From: jyn Date: Mon, 29 Jun 2026 09:43:54 +0200 Subject: [PATCH 11/28] enable `TIDY_PRINT_DIFF` by default for non-interactive cases, such as CI, this makes it easier to tell what went wrong without having to interactively debug the problem. --- src/tools/tidy/src/extra_checks/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs index 0ee5b3534dd91..75b02520a3d46 100644 --- a/src/tools/tidy/src/extra_checks/mod.rs +++ b/src/tools/tidy/src/extra_checks/mod.rs @@ -207,7 +207,9 @@ fn show_bless_help(mode: &str, action: &str, bless: bool) { } fn show_diff() -> bool { - std::env::var("TIDY_PRINT_DIFF").is_ok_and(|v| v.eq_ignore_ascii_case("true") || v == "1") + let disable_diff = + std::env::var("TIDY_PRINT_DIFF").is_ok_and(|v| v.eq_ignore_ascii_case("false") || v == "0"); + !disable_diff } fn check_spellcheck(root_path: &Path, outdir: &Path, cargo: &Path, tidy_ctx: &TidyCtx) { From e2af9c03ef4cfb769e391968a5cfbb1afe6f01e2 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Mon, 29 Jun 2026 20:05:45 +0200 Subject: [PATCH 12/28] borrowck: Introduce BlameConstraint::to_obligation_cause_from_path() Before this commit, `BlameConstraint` is a bit hard to understand at first, because of its `ObligationCause` field. In practice, `BlameConstraint` is just a slimmed down version of an `OutlivesConstraint` from `Vec`. The artificial `ObligationCause` is only needed in one place. Rather than having that exception polute the lower level analysis, bubble it up as much as we can. That way, it becomes clearer what `BlameConstraint` actually is. For reference, `ObligationCause` was introdued by 93ab12eeaba2c, but the codebase looked quite different back then. --- .../src/diagnostics/explain_borrow.rs | 4 +- .../src/diagnostics/region_errors.rs | 14 +++--- .../rustc_borrowck/src/region_infer/mod.rs | 49 +++++++++++-------- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index f6abafb874165..1e336f1e4509d 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -585,11 +585,11 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { NllRegionVariableOrigin::FreeRegion, outlived_region, ); - let BlameConstraint { category, from_closure, cause, .. } = blame_constraint; + let BlameConstraint { category, from_closure, span, .. } = blame_constraint; let outlived_fr_name = self.give_region_a_name(outlived_region); - (category, from_closure, cause.span, outlived_fr_name, path) + (category, from_closure, span, outlived_fr_name, path) } /// Returns structured explanation for *why* the borrow contains the diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index 1166e1265dadb..a82cd5c74c330 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -414,8 +414,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }; // Find the code to blame for the fact that `longer_fr` outlives `error_fr`. - let cause = - self.regioncx.best_blame_constraint(longer_fr, origin_longer, error_vid).0.cause; + let (blame_constraint, path) = + self.regioncx.best_blame_constraint(longer_fr, origin_longer, error_vid); + let cause = blame_constraint.to_obligation_cause_from_path(&path); // FIXME these methods should have better names, and also probably not be this generic. // FIXME note that we *throw away* the error element here! We probably want to @@ -448,17 +449,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let (blame_constraint, path) = self.regioncx.best_blame_constraint(fr, fr_origin, outlived_fr); - let BlameConstraint { category, cause, variance_info, .. } = blame_constraint; + let BlameConstraint { category, span, variance_info, .. } = blame_constraint; - debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); + debug!("report_region_error: category={:?} {:?} {:?}", category, span, variance_info); // Check if we can use one of the "nice region errors". if let (Some(f), Some(o)) = (self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr)) { let infer_err = self.infcx.err_ctxt(); - let nice = - NiceRegionError::new_from_span(&infer_err, self.mir_def_id(), cause.span, o, f); + let nice = NiceRegionError::new_from_span(&infer_err, self.mir_def_id(), span, o, f); if let Some(diag) = nice.try_report_from_nll() { self.buffer_error(diag); return; @@ -475,7 +475,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { fr_is_local, outlived_fr_is_local, category ); - let errci = ErrorConstraintInfo { fr, outlived_fr, category, span: cause.span }; + let errci = ErrorConstraintInfo { fr, outlived_fr, category, span }; let mut diag = match (category, fr_is_local, outlived_fr_is_local) { (ConstraintCategory::SolverRegionConstraint(span), _, _) => { diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 5e56ae80ff5da..64aa92c2c8878 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -1346,7 +1346,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { propagated_outlives_requirements.push(ClosureOutlivesRequirement { subject: ClosureOutlivesSubject::Region(fr_minus), outlived_free_region: fr_plus, - blame_span: blame_constraint.cause.span, + blame_span: blame_constraint.span, category: blame_constraint.category, }); } @@ -1652,24 +1652,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { .collect::>() ); - // We try to avoid reporting a `ConstraintCategory::Predicate` as our best constraint. - // Instead, we use it to produce an improved `ObligationCauseCode`. - // FIXME - determine what we should do if we encounter multiple - // `ConstraintCategory::Predicate` constraints. Currently, we just pick the first one. - let cause_code = path - .iter() - .find_map(|constraint| { - if let ConstraintCategory::Predicate(predicate_span) = constraint.category { - // We currently do not store the `DefId` in the `ConstraintCategory` - // for performances reasons. The error reporting code used by NLL only - // uses the span, so this doesn't cause any problems at the moment. - Some(ObligationCauseCode::WhereClause(CRATE_DEF_ID.to_def_id(), predicate_span)) - } else { - None - } - }) - .unwrap_or_else(|| ObligationCauseCode::Misc); - // When reporting an error, there is typically a chain of constraints leading from some // "source" region which must outlive some "target" region. // In most cases, we prefer to "blame" the constraints closer to the target -- @@ -1836,7 +1818,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let blame_constraint = BlameConstraint { category: best_constraint.category, from_closure: best_constraint.from_closure, - cause: ObligationCause::new(best_constraint.span, CRATE_DEF_ID, cause_code.clone()), + span: best_constraint.span, variance_info: best_constraint.variance_info, }; (blame_constraint, path) @@ -1917,6 +1899,31 @@ impl<'tcx> RegionInferenceContext<'tcx> { pub(crate) struct BlameConstraint<'tcx> { pub category: ConstraintCategory<'tcx>, pub from_closure: bool, - pub cause: ObligationCause<'tcx>, + pub span: Span, pub variance_info: ty::VarianceDiagInfo>, } + +impl<'tcx> BlameConstraint<'tcx> { + pub(crate) fn to_obligation_cause_from_path( + &self, + path: &[OutlivesConstraint<'tcx>], + ) -> ObligationCause<'tcx> { + // FIXME - determine what we should do if we encounter multiple + // `ConstraintCategory::Predicate` constraints. Currently, we just pick the first one. + let cause_code = path + .iter() + .find_map(|constraint| { + if let ConstraintCategory::Predicate(predicate_span) = constraint.category { + // We currently do not store the `DefId` in the `ConstraintCategory` + // for performances reasons. The error reporting code used by NLL only + // uses the span, so this doesn't cause any problems at the moment. + Some(ObligationCauseCode::WhereClause(CRATE_DEF_ID.to_def_id(), predicate_span)) + } else { + None + } + }) + .unwrap_or_else(|| ObligationCauseCode::Misc); + + ObligationCause::new(self.span, CRATE_DEF_ID, cause_code.clone()) + } +} From a3a8023742c639b5b8d055ef8d7ed14b71d8d566 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Wed, 29 Apr 2026 23:57:31 -0700 Subject: [PATCH 13/28] Update `transmute_copy` to ub_checks and `?Sized` --- library/core/src/mem/mod.rs | 44 +++++++++++-- library/coretests/tests/mem.rs | 26 -------- .../lib-optimizations/transmute_copy.rs | 65 +++++++++++++++++++ .../ui/precondition-checks/transmute_copy.rs | 9 +++ 4 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 tests/codegen-llvm/lib-optimizations/transmute_copy.rs create mode 100644 tests/ui/precondition-checks/transmute_copy.rs diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index a00a6c4783ba6..e24b220acb43b 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -35,6 +35,7 @@ use crate::clone::TrivialClone; use crate::cmp::Ordering; use crate::marker::{Destruct, DiscriminantKind}; use crate::panic::const_assert; +use crate::ub_checks::assert_unsafe_precondition; use crate::{clone, cmp, fmt, hash, intrinsics, ptr}; mod alignment; @@ -1077,6 +1078,27 @@ pub const fn copy(x: &T) -> T { /// /// [ub]: ../../reference/behavior-considered-undefined.html /// +/// If you have a raw pointer instead of a reference, you might be looking for +/// `src.cast::().`[`read_unaligned()`](pointer#method.read_unaligned) instead. +/// +/// # Safety +/// +/// - Requires `size_of_val::(src) >= size_of::()` +/// - The first `size_of::()` bytes behind `src` must be *readable* +/// - The first `size_of::()` bytes behind `src` must be *[valid]* +/// when interpreted as a `Dst`. +/// +/// On top of that, remember that most types have additional invariants beyond merely +/// being considered initialized at the type level. For example, a `1`-initialized [`Vec`] +/// is considered initialized (under the current implementation; this does not constitute +/// a stable guarantee) because the only requirement the compiler knows about it +/// is that the data pointer must be non-null. Creating such a `Vec` does not cause +/// *immediate* undefined behavior, but will cause undefined behavior with most +/// safe operations (including dropping it). +/// +/// [valid]: ../../reference/behavior-considered-undefined.html#r-undefined.validity +/// [`Vec`]: ../../std/vec/struct.Vec.html +/// /// # Examples /// /// ``` @@ -1101,20 +1123,32 @@ pub const fn copy(x: &T) -> T { /// /// // The contents of 'foo_array' should not have changed /// assert_eq!(foo_array, [10]); +/// +/// let bytes: &[u8] = &[1, 2, 3, 4, 5, 6, 7]; +/// assert_eq!( +/// unsafe { mem::transmute_copy::<[u8], u32>(bytes) }, +/// u32::from_ne_bytes(*bytes.first_chunk().unwrap()), +/// ); /// ``` #[inline] #[must_use] #[track_caller] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_transmute_copy", since = "1.74.0")] -pub const unsafe fn transmute_copy(src: &Src) -> Dst { - assert!( - size_of::() >= size_of::(), - "cannot transmute_copy if Dst is larger than Src" +pub const unsafe fn transmute_copy(src: &Src) -> Dst { + // library UB because it's possible for the `Src` to be only a subset of the allocation + // and thus for a failure to not be immediate language UB + assert_unsafe_precondition!( + check_library_ub, + "cannot transmute_copy if Dst is larger than Src", + ( + src_size: usize = size_of_val::(src), + dst_size: usize = Dst::SIZE, + ) => src_size >= dst_size ); // If Dst has a higher alignment requirement, src might not be suitably aligned. - if align_of::() > align_of::() { + if align_of::() > align_of_val::(src) { // SAFETY: `src` is a reference which is guaranteed to be valid for reads. // The caller must guarantee that the actual transmutation is safe. unsafe { ptr::read_unaligned(src as *const Src as *const Dst) } diff --git a/library/coretests/tests/mem.rs b/library/coretests/tests/mem.rs index b95e6e13063f5..793c9f489e47f 100644 --- a/library/coretests/tests/mem.rs +++ b/library/coretests/tests/mem.rs @@ -138,32 +138,6 @@ fn test_transmute_copy_unaligned() { assert_eq!(0_u64, unsafe { transmute_copy(&u.b) }); } -#[test] -#[cfg(panic = "unwind")] -fn test_transmute_copy_grow_panics() { - use std::panic; - - let err = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe { - let _unused: u64 = transmute_copy(&1_u8); - })); - - match err { - Ok(_) => unreachable!(), - Err(payload) => { - payload - .downcast::<&'static str>() - .and_then(|s| { - if *s == "cannot transmute_copy if Dst is larger than Src" { - Ok(s) - } else { - Err(s) - } - }) - .unwrap_or_else(|p| panic::resume_unwind(p)); - } - } -} - #[test] #[allow(dead_code)] fn test_discriminant_send_sync() { diff --git a/tests/codegen-llvm/lib-optimizations/transmute_copy.rs b/tests/codegen-llvm/lib-optimizations/transmute_copy.rs new file mode 100644 index 0000000000000..bc96fd08c74aa --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/transmute_copy.rs @@ -0,0 +1,65 @@ +//@ compile-flags: -Copt-level=1 +//@ only-64bit + +#![crate_type = "lib"] + +// Check that we don't have runtime alignment checks, just a safe alignment. +// (Rust emits a branch, but LLVM merges the loads from the arms.) + +use std::mem::transmute_copy; + +// CHECK-LABEL: @transmute_copy_i16_from_i32_slice +// CHECK-SAME: ptr{{.+}}%_0, +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: i64{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i16_from_i32_slice(x: &[i32]) -> [i16; 7] { + // CHECK: start: + // CHECK-NEXT: @llvm.memcpy + // CHECK-SAME: align 2{{.+}}%_0, + // CHECK-SAME: align 4{{.+}}%x.0, + // CHECK-SAME: i64 14, + // CHECK-NEXT: ret void + transmute_copy(x) +} + +// CHECK-LABEL: @transmute_copy_i32_from_i16_slice +// CHECK-SAME: ptr{{.+}}%_0, +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: i64{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i32_from_i16_slice(x: &[i16]) -> [i32; 3] { + // CHECK: start: + // CHECK-NEXT: @llvm.memcpy + // CHECK-SAME: align 4{{.+}}%_0, + // CHECK-SAME: align 2{{.+}}%x.0, + // CHECK-SAME: i64 12, + // CHECK-NEXT: ret void + transmute_copy(x) +} + +// CHECK-LABEL: i32 @transmute_copy_i32_from_dyn +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: ptr{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i32_from_dyn(x: &dyn std::fmt::Debug) -> i32 { + // CHECK: start: + // CHECK-NEXT: [[TEMP:%.+]] = load i32, ptr %x.0, align 1 + // CHECK-NEXT: ret i32 [[TEMP]] + transmute_copy(x) +} + +// CHECK-LABEL: @transmute_copy_i16_array_from_dyn +// CHECK-SAME: ptr{{.+}}%_0, +// CHECK-SAME: ptr{{.+}}%x.0, +// CHECK-SAME: ptr{{.+}}%x.1) +#[no_mangle] +pub unsafe fn transmute_copy_i16_array_from_dyn(x: &dyn std::fmt::Debug) -> [i16; 7] { + // CHECK: start: + // CHECK-NEXT: @llvm.memcpy + // CHECK-SAME: align 2{{.+}}%_0, + // CHECK-SAME: align 1{{.+}}%x.0, + // CHECK-SAME: i64 14, + // CHECK-NEXT: ret void + transmute_copy(x) +} diff --git a/tests/ui/precondition-checks/transmute_copy.rs b/tests/ui/precondition-checks/transmute_copy.rs new file mode 100644 index 0000000000000..160699a4c8ccd --- /dev/null +++ b/tests/ui/precondition-checks/transmute_copy.rs @@ -0,0 +1,9 @@ +//@ run-crash +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: cannot transmute_copy if Dst is larger than Src + +fn main() { + unsafe { + let _unused: u64 = std::mem::transmute_copy(&1_u8); + } +} From ab4a01496fdfff92c509edd5679ca73d4baf87a8 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Jul 2026 07:09:12 -0700 Subject: [PATCH 14/28] Add release notes for 1.96.1 Porting the notes from the stable branch. --- RELEASES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 9dfb5c86e3cae..2fa271401fbb3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,12 @@ +Version 1.96.1 (2026-06-30) +=========================== + + + +- [Cargo: fix timeout/retry behavior](https://github.com/rust-lang/cargo/pull/17131) +- [Cargo: apply patches for CVE-2025-15661, CVE-2026-55199, and CVE-2026-55200 to libssh2](https://github.com/rust-lang/cargo/pull/17140) +- [rustc: fix miscompilation in MIR optimization](https://github.com/rust-lang/rust/pull/158214) + Version 1.96.0 (2026-05-28) ========================== From d7e4edd9a1dac40e4518ecd2031d1274a1fcd144 Mon Sep 17 00:00:00 2001 From: sgasho Date: Fri, 3 Jul 2026 00:43:49 +0900 Subject: [PATCH 15/28] Add -Zautodiff_post_passes flag to limit which llvm passes to run after enzyme --- compiler/rustc_codegen_llvm/src/back/lto.rs | 4 +- compiler/rustc_codegen_llvm/src/back/write.rs | 10 + compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 2 + compiler/rustc_codegen_ssa/src/back/write.rs | 5 + compiler/rustc_interface/src/tests.rs | 1 + .../rustc_llvm/llvm-wrapper/PassWrapper.cpp | 215 +++++++++--------- compiler/rustc_session/src/options.rs | 2 + 7 files changed, 136 insertions(+), 103 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index 020c6668fb9d3..b2d22876c1858 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -620,7 +620,9 @@ pub(crate) fn run_pass_manager( if cfg!(feature = "llvm_enzyme") && enable_ad && !thin { let opt_stage = llvm::OptStage::FatLTO; let stage = write::AutodiffStage::PostAD; - if !config.autodiff.contains(&config::AutoDiff::NoPostopt) { + if !config.autodiff.contains(&config::AutoDiff::NoPostopt) + && config.autodiff_post_passes.as_deref() != Some("") + { unsafe { write::llvm_optimize( cgcx, prof, dcx, module, None, None, config, opt_level, opt_stage, stage, diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 4426f6ebb3c17..e44c579e06c83 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -566,6 +566,14 @@ pub(crate) unsafe fn llvm_optimize( let print_before_enzyme = config.autodiff.contains(&config::AutoDiff::PrintModBefore); let print_after_enzyme = config.autodiff.contains(&config::AutoDiff::PrintModAfter); let print_passes = config.autodiff.contains(&config::AutoDiff::PrintPasses); + let passes_after_enzyme = if autodiff_stage == AutodiffStage::PostAD { + config.autodiff_post_passes.as_deref() + } else { + None + }; + let passes_after_enzyme_ptr = + passes_after_enzyme.map_or(std::ptr::null(), |s| s.as_c_char_ptr()); + let passes_after_enzyme_len = passes_after_enzyme.map_or(0, |s| s.len()); let merge_functions; let unroll_loops; let vectorize_slp; @@ -795,6 +803,8 @@ pub(crate) unsafe fn llvm_optimize( llvm_selfprofiler, selfprofile_before_pass_callback, selfprofile_after_pass_callback, + passes_after_enzyme_ptr, + passes_after_enzyme_len, extra_passes.as_c_char_ptr(), extra_passes.len(), llvm_plugins.as_c_char_ptr(), diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index dd527eafe8362..206b2da9eb6e8 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2492,6 +2492,8 @@ unsafe extern "C" { llvm_selfprofiler: *mut c_void, begin_callback: SelfProfileBeforePassCallback, end_callback: SelfProfileAfterPassCallback, + PostEnzymePasses: *const c_char, + PostEnzymePassesLen: size_t, ExtraPasses: *const c_char, ExtraPassesLen: size_t, LLVMPlugins: *const c_char, diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 112cf45ebbf2e..8f3a4711424a9 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -108,6 +108,7 @@ pub struct ModuleConfig { pub emit_lifetime_markers: bool, pub llvm_plugins: Vec, pub autodiff: Vec, + pub autodiff_post_passes: Option, pub offload: Vec, } @@ -259,6 +260,10 @@ impl ModuleConfig { emit_lifetime_markers: sess.emit_lifetime_markers(), llvm_plugins: if_regular!(sess.opts.unstable_opts.llvm_plugins.clone(), vec![]), autodiff: if_regular!(sess.opts.unstable_opts.autodiff.clone(), vec![]), + autodiff_post_passes: if_regular!( + sess.opts.unstable_opts.autodiff_post_passes.clone(), + None + ), offload: if_regular!(sess.opts.unstable_opts.offload.clone(), vec![]), } } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 5933d0fd4b356..c43acb7363107 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -788,6 +788,7 @@ fn test_unstable_options_tracking_hash() { tracked!(annotate_moves, AnnotateMoves::Enabled(Some(1234))); tracked!(assume_incomplete_release, true); tracked!(autodiff, vec![AutoDiff::Enable, AutoDiff::NoTT]); + tracked!(autodiff_post_passes, Some("function(mem2reg,instsimplify,simplifycfg)".to_string())); tracked!(binary_dep_depinfo, true); tracked!(box_noalias, false); tracked!( diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index 5dcaa5f6f84b8..c15ab2e944db8 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -606,6 +606,7 @@ extern "C" LLVMRustResult LLVMRustOptimize( bool DebugInfoForProfiling, void *LlvmSelfProfiler, LLVMRustSelfProfileBeforePassCallback BeforePassCallback, LLVMRustSelfProfileAfterPassCallback AfterPassCallback, + const char *PostEnzymePasses, size_t PostEnzymePassesLen, const char *ExtraPasses, size_t ExtraPassesLen, const char *LLVMPlugins, size_t LLVMPluginsLen) { Module *TheModule = unwrap(ModuleRef); @@ -842,120 +843,130 @@ extern "C" LLVMRustResult LLVMRustOptimize( raw_string_ostream ThinLinkDataOS(ThinLTOSummaryBuffer->data); bool IsLTO = OptStage == LLVMRustOptStage::ThinLTO || OptStage == LLVMRustOptStage::FatLTO; - if (!NoPrepopulatePasses) { - for (const auto &C : PipelineStartEPCallbacks) - PB.registerPipelineStartEPCallback(C); - for (const auto &C : OptimizerLastEPCallbacks) - PB.registerOptimizerLastEPCallback(C); - - // The pre-link pipelines don't support O0 and require using - // buildO0DefaultPipeline() instead. At the same time, the LTO pipelines do - // support O0 and using them is required. - if (OptLevel == OptimizationLevel::O0 && !IsLTO) { - // We manually schedule ThinLTOBufferPasses below, so don't pass the value - // to enable it here. - MPM = PB.buildO0DefaultPipeline(OptLevel); - } else { - switch (OptStage) { - case LLVMRustOptStage::PreLinkNoLTO: - if (ThinLTOBufferRef) { - // This is similar to LLVM's `buildFatLTODefaultPipeline`, where the - // bitcode for embedding is obtained after performing - // `ThinLTOPreLinkDefaultPipeline`. - MPM.addPass(PB.buildThinLTOPreLinkDefaultPipeline(OptLevel)); - MPM.addPass(ThinLTOBitcodeWriterPass( - ThinLTODataOS, - ThinLTOSummaryBufferRef ? &ThinLinkDataOS : nullptr)); - *ThinLTOBufferRef = ThinLTOBuffer.release(); - if (ThinLTOSummaryBufferRef) { - *ThinLTOSummaryBufferRef = ThinLTOSummaryBuffer.release(); + if (PostEnzymePassesLen) { + if (auto Err = PB.parsePassPipeline( + MPM, StringRef(PostEnzymePasses, PostEnzymePassesLen))) { + std::string ErrMsg = toString(std::move(Err)); + LLVMRustSetLastError(ErrMsg.c_str()); + return LLVMRustResult::Failure; + } + } else { + if (!NoPrepopulatePasses) { + for (const auto &C : PipelineStartEPCallbacks) + PB.registerPipelineStartEPCallback(C); + for (const auto &C : OptimizerLastEPCallbacks) + PB.registerOptimizerLastEPCallback(C); + + // The pre-link pipelines don't support O0 and require using + // buildO0DefaultPipeline() instead. At the same time, the LTO pipelines + // do support O0 and using them is required. + if (OptLevel == OptimizationLevel::O0 && !IsLTO) { + // We manually schedule ThinLTOBufferPasses below, so don't pass the + // value to enable it here. + MPM = PB.buildO0DefaultPipeline(OptLevel); + } else { + switch (OptStage) { + case LLVMRustOptStage::PreLinkNoLTO: + if (ThinLTOBufferRef) { + // This is similar to LLVM's `buildFatLTODefaultPipeline`, where the + // bitcode for embedding is obtained after performing + // `ThinLTOPreLinkDefaultPipeline`. + MPM.addPass(PB.buildThinLTOPreLinkDefaultPipeline(OptLevel)); + MPM.addPass(ThinLTOBitcodeWriterPass( + ThinLTODataOS, + ThinLTOSummaryBufferRef ? &ThinLinkDataOS : nullptr)); + *ThinLTOBufferRef = ThinLTOBuffer.release(); + if (ThinLTOSummaryBufferRef) { + *ThinLTOSummaryBufferRef = ThinLTOSummaryBuffer.release(); + } + MPM.addPass(PB.buildModuleOptimizationPipeline( + OptLevel, ThinOrFullLTOPhase::None)); + MPM.addPass( + createModuleToFunctionPassAdaptor(AnnotationRemarksPass())); + } else { + MPM = PB.buildPerModuleDefaultPipeline(OptLevel); } - MPM.addPass(PB.buildModuleOptimizationPipeline( - OptLevel, ThinOrFullLTOPhase::None)); - MPM.addPass( - createModuleToFunctionPassAdaptor(AnnotationRemarksPass())); - } else { - MPM = PB.buildPerModuleDefaultPipeline(OptLevel); + break; + case LLVMRustOptStage::PreLinkThinLTO: + case LLVMRustOptStage::PreLinkFatLTO: + MPM = PB.buildThinLTOPreLinkDefaultPipeline(OptLevel); + NeedThinLTOBufferPasses = false; + break; + case LLVMRustOptStage::ThinLTO: + // FIXME: Does it make sense to pass the ModuleSummaryIndex? + // It only seems to be needed for C++ specific optimizations. + MPM = PB.buildThinLTODefaultPipeline(OptLevel, nullptr); + break; + case LLVMRustOptStage::FatLTO: + MPM = PB.buildLTODefaultPipeline(OptLevel, nullptr); + NeedThinLTOBufferPasses = false; + break; } - break; - case LLVMRustOptStage::PreLinkThinLTO: - case LLVMRustOptStage::PreLinkFatLTO: - MPM = PB.buildThinLTOPreLinkDefaultPipeline(OptLevel); - NeedThinLTOBufferPasses = false; - break; - case LLVMRustOptStage::ThinLTO: - // FIXME: Does it make sense to pass the ModuleSummaryIndex? - // It only seems to be needed for C++ specific optimizations. - MPM = PB.buildThinLTODefaultPipeline(OptLevel, nullptr); - break; - case LLVMRustOptStage::FatLTO: - MPM = PB.buildLTODefaultPipeline(OptLevel, nullptr); - NeedThinLTOBufferPasses = false; - break; } + } else { + // We're not building any of the default pipelines but we still want to + // add the verifier, instrumentation, etc passes if they were requested + for (const auto &C : PipelineStartEPCallbacks) + C(MPM, OptLevel); + for (const auto &C : OptimizerLastEPCallbacks) + C(MPM, OptLevel, ThinOrFullLTOPhase::None); } - } else { - // We're not building any of the default pipelines but we still want to - // add the verifier, instrumentation, etc passes if they were requested - for (const auto &C : PipelineStartEPCallbacks) - C(MPM, OptLevel); - for (const auto &C : OptimizerLastEPCallbacks) - C(MPM, OptLevel, ThinOrFullLTOPhase::None); - } - if (ExtraPassesLen) { - if (auto Err = - PB.parsePassPipeline(MPM, StringRef(ExtraPasses, ExtraPassesLen))) { - std::string ErrMsg = toString(std::move(Err)); - LLVMRustSetLastError(ErrMsg.c_str()); - return LLVMRustResult::Failure; + if (ExtraPassesLen) { + if (auto Err = PB.parsePassPipeline( + MPM, StringRef(ExtraPasses, ExtraPassesLen))) { + std::string ErrMsg = toString(std::move(Err)); + LLVMRustSetLastError(ErrMsg.c_str()); + return LLVMRustResult::Failure; + } } - } - if (NeedThinLTOBufferPasses) { - MPM.addPass(CanonicalizeAliasesPass()); - MPM.addPass(NameAnonGlobalPass()); - } - // For `-Copt-level=0`, and the pre-link fat/thin LTO stages. - if (ThinLTOBufferRef && *ThinLTOBufferRef == nullptr) { - // thin lto summaries prevent fat lto, so do not emit them if fat - // lto is requested. See PR #136840 for background information. - if (OptStage != LLVMRustOptStage::PreLinkFatLTO) { - MPM.addPass(ThinLTOBitcodeWriterPass( - ThinLTODataOS, ThinLTOSummaryBufferRef ? &ThinLinkDataOS : nullptr)); - } else { - MPM.addPass(BitcodeWriterPass(ThinLTODataOS)); + if (NeedThinLTOBufferPasses) { + MPM.addPass(CanonicalizeAliasesPass()); + MPM.addPass(NameAnonGlobalPass()); } - *ThinLTOBufferRef = ThinLTOBuffer.release(); - if (ThinLTOSummaryBufferRef) { - *ThinLTOSummaryBufferRef = ThinLTOSummaryBuffer.release(); + // For `-Copt-level=0`, and the pre-link fat/thin LTO stages. + if (ThinLTOBufferRef && *ThinLTOBufferRef == nullptr) { + // thin lto summaries prevent fat lto, so do not emit them if fat + // lto is requested. See PR #136840 for background information. + if (OptStage != LLVMRustOptStage::PreLinkFatLTO) { + MPM.addPass(ThinLTOBitcodeWriterPass( + ThinLTODataOS, + ThinLTOSummaryBufferRef ? &ThinLinkDataOS : nullptr)); + } else { + MPM.addPass(BitcodeWriterPass(ThinLTODataOS)); + } + *ThinLTOBufferRef = ThinLTOBuffer.release(); + if (ThinLTOSummaryBufferRef) { + *ThinLTOSummaryBufferRef = ThinLTOSummaryBuffer.release(); + } } - } - // now load "-enzyme" pass: - // With dlopen, ENZYME macro may not be defined, so check EnzymePtr directly - // In the case of debug builds with multiple codegen units, we might not - // have all function definitions available during the early compiler - // invocations. We therefore wait for the final lto step to run Enzyme. - if (EnzymePtr && IsLTO) { - - if (PrintBeforeEnzyme) { - // Handle the Rust flag `-Zautodiff=PrintModBefore`. - std::string Banner = "Module before EnzymeNewPM"; - MPM.addPass(PrintModulePass(outs(), Banner, true, false)); - } + // now load "-enzyme" pass: + // With dlopen, ENZYME macro may not be defined, so check EnzymePtr directly + // In the case of debug builds with multiple codegen units, we might not + // have all function definitions available during the early compiler + // invocations. We therefore wait for the final lto step to run Enzyme. + if (EnzymePtr && IsLTO) { + + if (PrintBeforeEnzyme) { + // Handle the Rust flag `-Zautodiff=PrintModBefore`. + std::string Banner = "Module before EnzymeNewPM"; + MPM.addPass(PrintModulePass(outs(), Banner, true, false)); + } - EnzymePtr(PB, false); - if (auto Err = PB.parsePassPipeline(MPM, "enzyme")) { - std::string ErrMsg = toString(std::move(Err)); - LLVMRustSetLastError(ErrMsg.c_str()); - return LLVMRustResult::Failure; - } + EnzymePtr(PB, false); + if (auto Err = PB.parsePassPipeline(MPM, "enzyme")) { + std::string ErrMsg = toString(std::move(Err)); + LLVMRustSetLastError(ErrMsg.c_str()); + return LLVMRustResult::Failure; + } - if (PrintAfterEnzyme) { - // Handle the Rust flag `-Zautodiff=PrintModAfter`. - std::string Banner = "Module after EnzymeNewPM"; - MPM.addPass(PrintModulePass(outs(), Banner, true, false)); + if (PrintAfterEnzyme) { + // Handle the Rust flag `-Zautodiff=PrintModAfter`. + std::string Banner = "Module after EnzymeNewPM"; + MPM.addPass(PrintModulePass(outs(), Banner, true, false)); + } } } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index efc8a70f4feb2..e2ff67dd2fcf1 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2294,6 +2294,8 @@ options! { `=LooseTypes` `=Inline` Multiple options can be combined with commas."), + autodiff_post_passes: Option = (None, parse_opt_string, [TRACKED], + "set llvm passes to run after enzyme (no passes run when it is empty)"), #[rustc_lint_opt_deny_field_access("use `Session::binary_dep_depinfo` instead of this field")] binary_dep_depinfo: bool = (false, parse_bool, [TRACKED], "include artifacts (sysroot, crate dependencies) used during compilation in dep-info \ From 2a9e45d646b39c48b5c1121a5cb359e546ac5c9c Mon Sep 17 00:00:00 2001 From: sgasho Date: Fri, 3 Jul 2026 00:44:39 +0900 Subject: [PATCH 16/28] Update LLVM IR checks in codegen-llvm/autodiff tests --- tests/codegen-llvm/autodiff/abi_handling.rs | 36 ++++++++++---------- tests/codegen-llvm/autodiff/autodiffv2.rs | 2 +- tests/codegen-llvm/autodiff/batched.rs | 32 ++++++++++------- tests/codegen-llvm/autodiff/generic.rs | 4 +-- tests/codegen-llvm/autodiff/identical_fnc.rs | 6 ++-- tests/codegen-llvm/autodiff/impl.rs | 2 +- tests/codegen-llvm/autodiff/scalar.rs | 22 +++++++----- tests/codegen-llvm/autodiff/sret.rs | 22 +++++++----- tests/codegen-llvm/autodiff/trait.rs | 2 +- tests/codegen-llvm/autodiff/typetree.rs | 2 +- tests/codegen-llvm/autodiff/void_ret.rs | 2 +- 11 files changed, 73 insertions(+), 59 deletions(-) diff --git a/tests/codegen-llvm/autodiff/abi_handling.rs b/tests/codegen-llvm/autodiff/abi_handling.rs index a8bc482fc293f..90a36823060cd 100644 --- a/tests/codegen-llvm/autodiff/abi_handling.rs +++ b/tests/codegen-llvm/autodiff/abi_handling.rs @@ -1,7 +1,7 @@ //@ revisions: debug release //@[debug] compile-flags: -Zautodiff=Enable,NoTT -C opt-level=0 -Clto=fat -//@[release] compile-flags: -Zautodiff=Enable,NoTT -C opt-level=3 -Clto=fat +//@[release] compile-flags: -Zautodiff=Enable,NoTT -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme @@ -39,15 +39,15 @@ fn square(x: f32) -> f32 { // CHECK-NEXT: Function Attrs // debug-NEXT: define internal { float, float } // debug-SAME: (ptr {{.*}}%x, ptr {{.*}}%bx_0) -// release-NEXT: define internal fastcc float -// release-SAME: (float %x.0.val, float %x.4.val) +// release-NEXT: define internal fastcc { float, float } +// release-SAME: (ptr {{.*}}%x) // CHECK-LABEL: ; abi_handling::f1 // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (ptr {{.*}}%x) -// release-NEXT: define internal fastcc noundef float -// release-SAME: (float %x.0.val, float %x.4.val) +// release-NEXT: define internal noundef float +// release-SAME: (ptr {{.*}}%x) #[autodiff_forward(df1, Dual, Dual)] #[inline(never)] fn f1(x: &[f32; 2]) -> f32 { @@ -58,15 +58,15 @@ fn f1(x: &[f32; 2]) -> f32 { // CHECK-NEXT: Function Attrs // debug-NEXT: define internal { float, float } // debug-SAME: (ptr %f, float %x, float %dret) -// release-NEXT: define internal fastcc noundef float +// release-NEXT: define internal fastcc { float, float } // release-SAME: (float noundef %x) // CHECK-LABEL: ; abi_handling::f2 // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (ptr %f, float %x) -// release-NEXT: define internal fastcc noundef float -// release-SAME: (float noundef %x) +// release-NEXT: define internal noundef float +// release-SAME: (ptr {{.*}}%f, float {{.*}}%x) #[autodiff_reverse(df2, Const, Active, Active)] #[inline(never)] fn f2(f: fn(f32) -> f32, x: f32) -> f32 { @@ -77,15 +77,15 @@ fn f2(f: fn(f32) -> f32, x: f32) -> f32 { // CHECK-NEXT: Function Attrs // debug-NEXT: define internal { float, float } // debug-SAME: (ptr align 4 %x, ptr align 4 %bx_0, ptr align 4 %y, ptr align 4 %by_0) -// release-NEXT: define internal fastcc float -// release-SAME: (float %x.0.val) +// release-NEXT: define internal fastcc { float, float } +// release-SAME: (ptr {{.*}}%x) // CHECK-LABEL: ; abi_handling::f3 // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (ptr {{.*}}%x, ptr {{.*}}%y) -// release-NEXT: define internal fastcc noundef float -// release-SAME: (float %x.0.val) +// release-NEXT: define internal noundef float +// release-SAME: (ptr {{.*}}%y) #[autodiff_forward(df3, Dual, Dual, Dual)] #[inline(never)] fn f3<'a>(x: &'a f32, y: &'a f32) -> f32 { @@ -103,7 +103,7 @@ fn f3<'a>(x: &'a f32, y: &'a f32) -> f32 { // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (float %x.0, float %x.1) -// release-NEXT: define internal fastcc noundef float +// release-NEXT: define internal noundef float // release-SAME: (float noundef %x.0, float noundef %x.1) #[autodiff_forward(df4, Dual, Dual)] #[inline(never)] @@ -122,7 +122,7 @@ fn f4(x: (f32, f32)) -> f32 { // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (float %i.0, float %i.1) -// release-NEXT: define internal fastcc noundef float +// release-NEXT: define internal noundef float // release-SAME: (float noundef %i.0, float noundef %i.1) #[autodiff_forward(df5, Dual, Dual)] #[inline(never)] @@ -142,7 +142,7 @@ fn f5(i: Input) -> f32 { // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (float %i.0, float %i.1) -// release-NEXT: define internal fastcc noundef float +// release-NEXT: define internal noundef float // release-SAME: (float noundef %i.0, float noundef %i.1) #[autodiff_forward(df6, Dual, Dual)] #[inline(never)] @@ -155,14 +155,14 @@ fn f6(i: NestedInput) -> f32 { // debug-NEXT: define internal { float, float } // debug-SAME: (ptr align 4 %x.0, ptr align 4 %x.1, ptr align 4 %bx_0.0, ptr align 4 %bx_0.1) // release-NEXT: define internal fastcc { float, float } -// release-SAME: (float %x.0.0.val, float %x.1.0.val) +// release-SAME: (ptr {{.*}}%x.0, ptr {{.*}}%x.1) // CHECK-LABEL: ; abi_handling::f7 // CHECK-NEXT: Function Attrs // debug-NEXT: define internal float // debug-SAME: (ptr {{.*}}%x.0, ptr {{.*}}%x.1) -// release-NEXT: define internal fastcc noundef float -// release-SAME: (float %x.0.0.val, float %x.1.0.val) +// release-NEXT: define internal noundef float +// release-SAME: (ptr {{.*}}%x.0, ptr {{.*}}%x.1) #[autodiff_forward(df7, Dual, Dual)] #[inline(never)] fn f7(x: (&f32, &f32)) -> f32 { diff --git a/tests/codegen-llvm/autodiff/autodiffv2.rs b/tests/codegen-llvm/autodiff/autodiffv2.rs index c24a374148c34..10e619f84335e 100644 --- a/tests/codegen-llvm/autodiff/autodiffv2.rs +++ b/tests/codegen-llvm/autodiff/autodiffv2.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable,NoTT -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme // diff --git a/tests/codegen-llvm/autodiff/batched.rs b/tests/codegen-llvm/autodiff/batched.rs index 5a723ff041839..64d14a0f9c80e 100644 --- a/tests/codegen-llvm/autodiff/batched.rs +++ b/tests/codegen-llvm/autodiff/batched.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable,NoTT,NoPostopt -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable,NoTT -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme @@ -20,22 +20,28 @@ fn square(x: &f32) -> f32 { x * x } -// The base ("scalar") case d_square3, without batching. -// CHECK: define internal fastcc float @fwddiffesquare(float %x.0.val, float %"x'.0.val") -// CHECK: %0 = fadd fast float %"x'.0.val", %"x'.0.val" -// CHECK-NEXT: %1 = fmul fast float %0, %x.0.val -// CHECK-NEXT: ret float %1 -// CHECK-NEXT: } - -// d_square2 -// CHECK: define internal fastcc [4 x float] @fwddiffe4square(float %x.0.val, [4 x ptr] %"x'") -// CHECK: ret [4 x float] +// CHECK: ; batched::d_square2 +// CHECK: define internal fastcc void +// CHECK-SAME: (ptr {{.*}}, ptr {{.*}}, ptr {{.*}}, ptr {{.*}}, ptr {{.*}}) +// CHECK: ret void // CHECK-NEXT: } -// CHECK: define internal fastcc { float, [4 x float] } @fwddiffe4square.{{.*}}(float %x.0.val, [4 x ptr] %"x'") -// CHECK: ret { float, [4 x float] } +// CHECK: ; batched::d_square1 +// CHECK: define internal fastcc void +// CHECK-SAME: (ptr {{.*}}, ptr {{.*}}, ptr {{.*}}, ptr {{.*}}, ptr {{.*}}, ptr {{.*}}) +// CHECK: ret void // CHECK-NEXT: } +// The base ("scalar") case d_square3, without batching. +// CHECK: define internal float @fwddiffesquare(ptr {{.*}}, ptr {{.*}}) +// CHECK: [[SHADOW_X:%"_[0-9]+'ipl"]] = load float, ptr %"x'" +// CHECK-NEXT: [[PRIMAL_X:%_[0-9]+]] = load float, ptr %x +// CHECK-NEXT: [[MUL1:%[0-9]+]] = fmul fast float [[SHADOW_X]], [[PRIMAL_X]] +// CHECK-NEXT: [[MUL2:%[0-9]+]] = fmul fast float [[SHADOW_X]], [[PRIMAL_X]] +// CHECK-NEXT: [[ADD1:%[0-9]+]] = fadd fast float [[MUL1]], [[MUL2]] +// CHECK-NEXT: ret float [[ADD1]] +// CHECK-NEXT: } + fn main() { let x = std::hint::black_box(3.0); let output = square(&x); diff --git a/tests/codegen-llvm/autodiff/generic.rs b/tests/codegen-llvm/autodiff/generic.rs index b31468c91c9c2..1c7ce7c544db5 100644 --- a/tests/codegen-llvm/autodiff/generic.rs +++ b/tests/codegen-llvm/autodiff/generic.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable -Zautodiff=NoPostopt -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme //@ revisions: F32 F64 Main @@ -34,7 +34,7 @@ fn square + Copy>(x: &T) -> T { // F64-NEXT: ; Function Attrs: {{.*}} // F64-NEXT: define internal {{.*}} void // F64-NEXT: start: -// F64-NEXT: {{(tail )?}}call {{(fastcc )?}}void @diffe_{{.*}}(double {{.*}}, ptr {{.*}}) +// F64-NEXT: {{(tail )?}}call fast { double } @diffe_{{.*}}(ptr {{.*}}, ptr {{.*}}, double {{.*}}) // F64-NEXT: ret void // Main-LABEL: ; generic::main diff --git a/tests/codegen-llvm/autodiff/identical_fnc.rs b/tests/codegen-llvm/autodiff/identical_fnc.rs index a8b186c302ea1..294ffaa8f719b 100644 --- a/tests/codegen-llvm/autodiff/identical_fnc.rs +++ b/tests/codegen-llvm/autodiff/identical_fnc.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable -Zautodiff_post_passes=mergefunc,function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme // @@ -32,9 +32,9 @@ fn square2(x: &f64) -> f64 { // CHECK-NOT:br // CHECK-NOT:ret // CHECK:; call identical_fnc::d_square -// CHECK-NEXT:call fastcc void @[[HASH:.+]](double %x.val, ptr noalias noundef align 8 dereferenceable(8) %dx1) +// CHECK-NEXT:call fastcc void @[[HASH:.+]](ptr {{.*}}, ptr {{.*}}) // CHECK:; call identical_fnc::d_square -// CHECK-NEXT:call fastcc void @[[HASH]](double %x.val, ptr noalias noundef align 8 dereferenceable(8) %dx2) +// CHECK-NEXT:call fastcc void @[[HASH]](ptr {{.*}}, ptr {{.*}}) fn main() { let x = std::hint::black_box(3.0); diff --git a/tests/codegen-llvm/autodiff/impl.rs b/tests/codegen-llvm/autodiff/impl.rs index 185ea6af52e0f..ffcf28e472e8b 100644 --- a/tests/codegen-llvm/autodiff/impl.rs +++ b/tests/codegen-llvm/autodiff/impl.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable -Zautodiff=NoPostopt -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme diff --git a/tests/codegen-llvm/autodiff/scalar.rs b/tests/codegen-llvm/autodiff/scalar.rs index 53672a89230ab..74e0bc6e32099 100644 --- a/tests/codegen-llvm/autodiff/scalar.rs +++ b/tests/codegen-llvm/autodiff/scalar.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable,NoTT -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable,NoTT -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme #![feature(autodiff)] @@ -12,14 +12,18 @@ fn square(x: &f64) -> f64 { x * x } -// CHECK:define internal fastcc double @diffesquare(double %x.0.val, ptr nonnull align 8 captures(none) %"x'") -// CHECK-NEXT:invertstart: -// CHECK-NEXT: %_0 = fmul double %x.0.val, %x.0.val -// CHECK-NEXT: %0 = fadd fast double %x.0.val, %x.0.val -// CHECK-NEXT: %1 = load double, ptr %"x'", align 8 -// CHECK-NEXT: %2 = fadd fast double %1, %0 -// CHECK-NEXT: store double %2, ptr %"x'", align 8 -// CHECK-NEXT: ret double %_0 +// CHECK:define internal { double } @diffesquare(ptr {{.*}}, ptr {{.*}}, double {{.*}}) +// CHECK-NEXT:start: +// CHECK-NEXT: [[X:%_[0-9]+]] = load double, ptr %x, align 8 +// CHECK-NEXT: [[SQUARE:%_[0-9]+]] = fmul double [[X]], [[X]] +// CHECK-NEXT: [[DIFFR1:%[0-9]+]] = fmul fast double %differeturn, [[X]] +// CHECK-NEXT: [[DIFFR2:%[0-9]+]] = fmul fast double %differeturn, [[X]] +// CHECK-NEXT: [[ADD1:%[0-9]+]] = fadd fast double [[DIFFR1]], [[DIFFR2]] +// CHECK-NEXT: [[SHADOW_X:%[0-9]+]] = load double, ptr %"x'", align 8 +// CHECK-NEXT: [[ADD2:%[0-9]+]] = fadd fast double [[SHADOW_X]], [[ADD1]] +// CHECK-NEXT: store double [[ADD2]], ptr %"x'", align 8 +// CHECK-NEXT: [[RET:%[0-9]+]] = insertvalue { double } undef, double [[SQUARE]], 0 +// CHECK-NEXT: ret { double } [[RET]] // CHECK-NEXT:} fn main() { diff --git a/tests/codegen-llvm/autodiff/sret.rs b/tests/codegen-llvm/autodiff/sret.rs index 498cd3fea012d..00ad0a58ffd08 100644 --- a/tests/codegen-llvm/autodiff/sret.rs +++ b/tests/codegen-llvm/autodiff/sret.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable,NoTT -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable,NoTT -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme @@ -18,17 +18,21 @@ fn primal(x: f32, y: f32) -> f64 { (x * x * y) as f64 } -// CHECK: define internal fastcc { double, float, float } @diffeprimal(float noundef %x, float noundef %y) -// CHECK-NEXT: invertstart: +// CHECK: define internal { double, float, float } @diffeprimal(float noundef %x, float noundef %y, double %differeturn) +// CHECK-NEXT: start: // CHECK-NEXT: %_4 = fmul float %x, %x // CHECK-NEXT: %_3 = fmul float %_4, %y // CHECK-NEXT: %_0 = fpext float %_3 to double -// CHECK-NEXT: %0 = fadd fast float %y, %y -// CHECK-NEXT: %1 = fmul fast float %0, %x -// CHECK-NEXT: %2 = insertvalue { double, float, float } undef, double %_0, 0 -// CHECK-NEXT: %3 = insertvalue { double, float, float } %2, float %1, 1 -// CHECK-NEXT: %4 = insertvalue { double, float, float } %3, float %_4, 2 -// CHECK-NEXT: ret { double, float, float } %4 +// CHECK-NEXT: %0 = fptrunc fast double %differeturn to float +// CHECK-NEXT: %1 = fmul fast float %0, %y +// CHECK-NEXT: %2 = fmul fast float %0, %_4 +// CHECK-NEXT: %3 = fmul fast float %1, %x +// CHECK-NEXT: %4 = fmul fast float %1, %x +// CHECK-NEXT: %5 = fadd fast float %3, %4 +// CHECK-NEXT: %6 = insertvalue { double, float, float } undef, double %_0, 0 +// CHECK-NEXT: %7 = insertvalue { double, float, float } %6, float %5, 1 +// CHECK-NEXT: %8 = insertvalue { double, float, float } %7, float %2, 2 +// CHECK-NEXT: ret { double, float, float } %8 // CHECK-NEXT: } fn main() { diff --git a/tests/codegen-llvm/autodiff/trait.rs b/tests/codegen-llvm/autodiff/trait.rs index 701f3a9e843bd..f82ceac2512dc 100644 --- a/tests/codegen-llvm/autodiff/trait.rs +++ b/tests/codegen-llvm/autodiff/trait.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable -Zautodiff=NoPostopt -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme diff --git a/tests/codegen-llvm/autodiff/typetree.rs b/tests/codegen-llvm/autodiff/typetree.rs index 1cb0c2fb68be3..4c270c3ad58ec 100644 --- a/tests/codegen-llvm/autodiff/typetree.rs +++ b/tests/codegen-llvm/autodiff/typetree.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme diff --git a/tests/codegen-llvm/autodiff/void_ret.rs b/tests/codegen-llvm/autodiff/void_ret.rs index 98c6b98eef4e3..e80c93d23a5be 100644 --- a/tests/codegen-llvm/autodiff/void_ret.rs +++ b/tests/codegen-llvm/autodiff/void_ret.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zautodiff=Enable,NoTT,NoPostopt -C no-prepopulate-passes -C opt-level=3 -Clto=fat +//@ compile-flags: -Zautodiff=Enable,NoTT -Zautodiff_post_passes=function(mem2reg,instsimplify,simplifycfg) -C no-prepopulate-passes -C opt-level=3 -Clto=fat //@ no-prefer-dynamic //@ needs-enzyme From cc7cef56d2fba4d3caceace0d3742076241cffe5 Mon Sep 17 00:00:00 2001 From: "addie.sh" Date: Thu, 2 Jul 2026 20:57:27 +0200 Subject: [PATCH 17/28] update TypeVisitable implementation --- compiler/rustc_type_ir/src/visit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_type_ir/src/visit.rs b/compiler/rustc_type_ir/src/visit.rs index b77782b09ef85..e205855e146f5 100644 --- a/compiler/rustc_type_ir/src/visit.rs +++ b/compiler/rustc_type_ir/src/visit.rs @@ -167,7 +167,7 @@ impl, E: TypeVisitable> TypeVisitable for } } -impl> TypeVisitable for &T { +impl + ?Sized> TypeVisitable for &T { fn visit_with>(&self, visitor: &mut V) -> V::Result { (**self).visit_with(visitor) } @@ -179,7 +179,7 @@ impl> TypeVisitable for Arc { } } -impl> TypeVisitable for Box { +impl + ?Sized> TypeVisitable for Box { fn visit_with>(&self, visitor: &mut V) -> V::Result { (**self).visit_with(visitor) } @@ -209,14 +209,14 @@ impl, const N: usize> TypeVisitable for Smal // `TypeFoldable` isn't impl'd for `&[T]`. It doesn't make sense in the general // case, because we can't return a new slice. But note that there are a couple // of trivial impls of `TypeFoldable` for specific slice types elsewhere. -impl> TypeVisitable for &[T] { +impl> TypeVisitable for [T] { fn visit_with>(&self, visitor: &mut V) -> V::Result { walk_visitable_list!(visitor, self.iter()); V::Result::output() } } -impl> TypeVisitable for Box<[T]> { +impl> TypeVisitable for [T; N] { fn visit_with>(&self, visitor: &mut V) -> V::Result { walk_visitable_list!(visitor, self.iter()); V::Result::output() From d42cca59aedb23cdc11b40621e3ddd1ea117137c Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Thu, 2 Jul 2026 22:39:40 +0300 Subject: [PATCH 18/28] Refactor delegation AST -> HIR lowering --- .../src/delegation/attributes.rs | 86 ++++ .../src/delegation/generics.rs | 297 +++++++++----- .../src/{delegation.rs => delegation/mod.rs} | 383 ++---------------- .../src/delegation/resolution.rs | 264 ++++++++++++ compiler/rustc_ast_lowering/src/item.rs | 6 +- 5 files changed, 574 insertions(+), 462 deletions(-) create mode 100644 compiler/rustc_ast_lowering/src/delegation/attributes.rs rename compiler/rustc_ast_lowering/src/{delegation.rs => delegation/mod.rs} (58%) create mode 100644 compiler/rustc_ast_lowering/src/delegation/resolution.rs diff --git a/compiler/rustc_ast_lowering/src/delegation/attributes.rs b/compiler/rustc_ast_lowering/src/delegation/attributes.rs new file mode 100644 index 0000000000000..88bd03389c14c --- /dev/null +++ b/compiler/rustc_ast_lowering/src/delegation/attributes.rs @@ -0,0 +1,86 @@ +use rustc_hir::attrs::{AttributeKind, InlineAttr}; +use rustc_hir::{self as hir}; +use rustc_span::Span; +use rustc_span::def_id::DefId; + +use crate::LoweringContext; +use crate::delegation::DelegationResolution; + +struct AdditionInfo { + pub equals: fn(&hir::Attribute) -> bool, + pub kind: AdditionKind, +} + +enum AdditionKind { + Default { factory: fn(Span) -> hir::Attribute }, + Inherit { factory: fn(Span, &hir::Attribute) -> hir::Attribute }, +} + +static ADDITIONS: &[AdditionInfo] = &[ + AdditionInfo { + equals: |a| matches!(a, hir::Attribute::Parsed(AttributeKind::MustUse { .. })), + kind: AdditionKind::Inherit { + factory: |span, original_attr| { + let reason = match original_attr { + hir::Attribute::Parsed(AttributeKind::MustUse { reason, .. }) => *reason, + _ => None, + }; + + hir::Attribute::Parsed(AttributeKind::MustUse { span, reason }) + }, + }, + }, + AdditionInfo { + equals: |a| matches!(a, hir::Attribute::Parsed(AttributeKind::Inline(..))), + kind: AdditionKind::Default { + factory: |span| hir::Attribute::Parsed(AttributeKind::Inline(InlineAttr::Hint, span)), + }, + }, +]; + +impl<'hir> LoweringContext<'_, 'hir> { + pub(super) fn add_attrs_if_needed(&mut self, resolution: &DelegationResolution) { + let &DelegationResolution { span, sig_id, .. } = resolution; + + const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; + let new_attrs = self.create_new_attrs(span, sig_id, self.attrs.get(&PARENT_ID)); + + if !new_attrs.is_empty() { + let new_attrs = match self.attrs.get(&PARENT_ID) { + Some(existing_attrs) => self.arena.alloc_from_iter( + existing_attrs.iter().map(|a| a.clone()).chain(new_attrs.into_iter()), + ), + None => self.arena.alloc_from_iter(new_attrs.into_iter()), + }; + + self.attrs.insert(PARENT_ID, new_attrs); + } + } + + fn create_new_attrs( + &self, + span: Span, + sig_id: DefId, + existing: Option<&&[hir::Attribute]>, + ) -> Vec { + ADDITIONS + .iter() + .filter_map(|addition| { + existing + .is_none_or(|attrs| !attrs.iter().any(|a| (addition.equals)(a))) + .then(|| match addition.kind { + AdditionKind::Default { factory } => Some(factory(span)), + AdditionKind::Inherit { factory, .. } => + { + #[allow(deprecated)] + self.tcx + .get_all_attrs(sig_id) + .iter() + .find_map(|a| (addition.equals)(a).then(|| factory(span, a))) + } + }) + .flatten() + }) + .collect::>() + } +} diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index bdbaa9c39e21a..cd7b23c18971d 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -4,12 +4,13 @@ use rustc_ast::*; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def_id::DefId; -use rustc_middle::ty::GenericParamDefKind; +use rustc_middle::ty::{GenericParamDefKind, TyCtxt}; use rustc_middle::{bug, ty}; use rustc_span::symbol::kw; use rustc_span::{Ident, Span, sym}; use crate::LoweringContext; +use crate::delegation::resolution::resolver::DelegationResolver; use crate::diagnostics::DelegationInfersMismatch; #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -237,74 +238,130 @@ impl<'hir> GenericsGenerationResult<'hir> { } } -impl<'hir> GenericsGenerationResults<'hir> { - pub(super) fn all_params(&self) -> impl Iterator> { - let parent = self.parent.generics.hir_generics_or_empty().params; - let child = self.child.generics.hir_generics_or_empty().params; +enum ParentSegmentArgs<'a> { + /// Parent segment is valid and generic args are specified: + /// `reuse Trait::<'static, ()>::foo;`. + Specified(&'a AngleBracketedArgs), + /// Parent segment is valid and args are not specified: + /// `reuse Trait::foo;`. + NotSpecified, + /// Parent segment does not exist (`reuse foo`) or we can not + /// add generics to it: + /// ```rust + /// mod to_reuse { + /// fn foo() {} + /// } + /// + /// // Can't add generic args to module. + /// reuse to_reuse::foo; + /// ``` + Invalid, +} - // Order generics, first we have parent and child lifetimes, - // then parent and child types and consts. - // `generics_of` in `rustc_hir_analysis` will order them anyway, - // however we want the order to be consistent in HIR too. - parent - .iter() - .filter(|p| p.is_lifetime()) - .chain(child.iter().filter(|p| p.is_lifetime())) - .chain(parent.iter().filter(|p| !p.is_lifetime())) - .chain(child.iter().filter(|p| !p.is_lifetime())) - .copied() - } +struct GenericsResolution<'a, 'tcx> { + trait_impl: bool, - /// As we add hack predicates(`'a: 'a`) for all lifetimes (see `uplift_delegation_generic_params` - /// and `generate_lifetime_predicate` functions) we need to add them to delegation generics. - /// Those predicates will not affect resulting predicate inheritance and folding - /// in `rustc_hir_analysis`, as we inherit all predicates from delegation signature. - pub(super) fn all_predicates(&self) -> impl Iterator> { - self.parent - .generics - .hir_generics_or_empty() - .predicates - .into_iter() - .chain(self.child.generics.hir_generics_or_empty().predicates) - .copied() - } + parent_args: ParentSegmentArgs<'a>, + child_args: Option<&'a AngleBracketedArgs>, + + sig_parent_params: &'tcx [ty::GenericParamDef], + sig_child_params: &'tcx [ty::GenericParamDef], + + free_to_trait_delegation: bool, + /// `reuse Trait::foo;`. + qself_is_none: bool, + /// `reuse <_ as Trait>::foo;`. + qself_is_infer: bool, + /// Whether we should generate `Self` generic param. + generate_self: bool, } -impl<'hir> LoweringContext<'_, 'hir> { - pub(super) fn uplift_delegation_generics( - &mut self, - delegation: &Delegation, +impl<'hir> DelegationResolver<'_, 'hir> { + fn resolve_generics<'a>( + &self, + delegation: &'a Delegation, sig_id: DefId, - ) -> GenericsGenerationResults<'hir> { - let delegation_parent_kind = self.tcx.def_kind(self.tcx.local_parent(self.owner.def_id)); + ) -> GenericsResolution<'a, 'hir> { + let tcx = self.tcx(); + let delegation_parent_kind = tcx.def_kind(tcx.local_parent(self.owner_id())); - let segments = &delegation.path.segments; - let len = segments.len(); + let delegation_in_free_ctx = + !matches!(delegation_parent_kind, DefKind::Trait | DefKind::Impl { .. }); - let get_user_args = |idx: usize| -> Option<&AngleBracketedArgs> { - let segment = &segments[idx]; + let sig_parent = tcx.parent(sig_id); + let sig_in_trait = matches!(tcx.def_kind(sig_parent), DefKind::Trait); + let free_to_trait_delegation = delegation_in_free_ctx && sig_in_trait; - let Some(args) = segment.args.as_ref() else { return None }; - let GenericArgs::AngleBracketed(args) = args else { - self.tcx.dcx().span_delayed_bug( - segment.span(), - "expected angle-bracketed generic args in delegation segment", - ); + let mut sig_parent_params: &[ty::GenericParamDef] = &[]; - return None; - }; + let qself_is_infer = + delegation.qself.as_ref().is_some_and(|qself| qself.ty.is_maybe_parenthesised_infer()); - // Treat empty args `reuse foo::<> as bar` as `reuse foo as bar`, - // the same logic applied when we call function `fn f(t: T)` - // like that `f::<>(())`, in HIR no `<>` will be generated. - (!args.args.is_empty()).then(|| args) + let qself_is_none = delegation.qself.is_none(); + + let parent_args = if let [.., parent_segment, _] = &delegation.path.segments[..] { + if let Some(res) = self.get_resolution_id(parent_segment.id) + && matches!(tcx.def_kind(res), DefKind::Trait | DefKind::TraitAlias) + { + sig_parent_params = &tcx.generics_of(sig_parent).own_params; + self.get_user_args(parent_segment) + .map(|args| ParentSegmentArgs::Specified(args)) + .unwrap_or(ParentSegmentArgs::NotSpecified) + } else { + ParentSegmentArgs::Invalid + } + } else { + ParentSegmentArgs::Invalid }; - let sig_params = &self.tcx.generics_of(sig_id).own_params[..]; + GenericsResolution { + parent_args, + sig_parent_params, + qself_is_none, + qself_is_infer, + free_to_trait_delegation, + generate_self: free_to_trait_delegation && (qself_is_none || qself_is_infer), + trait_impl: matches!(delegation_parent_kind, DefKind::Impl { of_trait: true }), + sig_child_params: &tcx.generics_of(sig_id).own_params, + child_args: self.get_user_args( + delegation.path.segments.last().expect("must be at least one segment"), + ), + } + } + + fn get_user_args<'a>(&self, segment: &'a PathSegment) -> Option<&'a AngleBracketedArgs> { + let Some(args) = &segment.args else { return None }; + let GenericArgs::AngleBracketed(args) = args else { + self.tcx().dcx().span_delayed_bug( + segment.span(), + "expected angle-bracketed generic args in delegation segment", + ); + + return None; + }; + + // Treat empty args `reuse foo::<> as bar` as `reuse foo as bar`, + // the same logic applied when we call function `fn f(t: T)` + // like that `f::<>(())`, in HIR no `<>` will be generated. + (!args.args.is_empty()).then(|| args) + } + + pub(super) fn resolve_and_generate_generics( + &self, + delegation: &Delegation, + sig_id: DefId, + ) -> GenericsGenerationResults<'hir> { + let res @ GenericsResolution { + trait_impl, + generate_self, + sig_child_params, + sig_parent_params, + .. + } = self.resolve_generics(delegation, sig_id); // If we are in trait impl always generate function whose generics matches // those that are defined in trait. - if matches!(delegation_parent_kind, DefKind::Impl { of_trait: true }) { + if trait_impl { // Considering parent generics, during signature inheritance // we will take those args that are in trait impl header trait ref. let parent = @@ -312,78 +369,65 @@ impl<'hir> LoweringContext<'_, 'hir> { let parent = GenericsGenerationResult::new(parent); - let child = DelegationGenerics::generate_all(sig_params, GenericsPosition::Child, true); + let child = + DelegationGenerics::generate_all(sig_child_params, GenericsPosition::Child, true); + let child = GenericsGenerationResult::new(child); return GenericsGenerationResults { parent, child, self_ty_propagation_kind: None }; } - let delegation_in_free_ctx = - !matches!(delegation_parent_kind, DefKind::Trait | DefKind::Impl { .. }); - - let sig_parent = self.tcx.parent(sig_id); - let sig_in_trait = matches!(self.tcx.def_kind(sig_parent), DefKind::Trait); - let free_to_trait_delegation = delegation_in_free_ctx && sig_in_trait; - - let qself_is_infer = - delegation.qself.as_ref().is_some_and(|qself| qself.ty.is_maybe_parenthesised_infer()); - - let qself_is_none = delegation.qself.is_none(); - - let generate_self = free_to_trait_delegation && (qself_is_none || qself_is_infer); - - let can_add_generics_to_parent = len >= 2 - && self.get_resolution_id(segments[len - 2].id).is_some_and(|def_id| { - matches!(self.tcx.def_kind(def_id), DefKind::Trait | DefKind::TraitAlias) - }); - - let parent_generics = if can_add_generics_to_parent { - let sig_parent_params = &self.tcx.generics_of(sig_parent).own_params; - - if let Some(args) = get_user_args(len - 2) { - DelegationGenerics { - data: self.create_slots_from_args( - args, - &sig_parent_params[usize::from(!generate_self)..], - generate_self, - ), - pos: GenericsPosition::Parent, - trait_impl: false, - } - } else { - DelegationGenerics::generate_all( + let tcx = self.tcx(); + let parent_generics = match res.parent_args { + ParentSegmentArgs::Specified(args) => DelegationGenerics { + data: Self::create_slots_from_args( + tcx, + args, &sig_parent_params[usize::from(!generate_self)..], - GenericsPosition::Parent, - false, - ) + generate_self, + ), + pos: GenericsPosition::Parent, + trait_impl, + }, + ParentSegmentArgs::NotSpecified => DelegationGenerics::generate_all( + &sig_parent_params[usize::from(!generate_self)..], + GenericsPosition::Parent, + trait_impl, + ), + ParentSegmentArgs::Invalid => { + DelegationGenerics { data: vec![], pos: GenericsPosition::Parent, trait_impl } } - } else { - DelegationGenerics { data: vec![], pos: GenericsPosition::Parent, trait_impl: false } }; - let child_generics = if let Some(args) = get_user_args(len - 1) { - let synth_params_index = - sig_params.iter().position(|p| p.kind.is_synthetic()).unwrap_or(sig_params.len()); + let child_generics = if let Some(args) = res.child_args { + let synth_params_index = sig_child_params + .iter() + .position(|p| p.kind.is_synthetic()) + .unwrap_or(sig_child_params.len()); - let mut slots = - self.create_slots_from_args(args, &sig_params[..synth_params_index], false); + let mut slots = Self::create_slots_from_args( + tcx, + args, + &sig_child_params[..synth_params_index], + trait_impl, + ); - for synth_param in &sig_params[synth_params_index..] { + for synth_param in &sig_child_params[synth_params_index..] { slots.push(GenericArgSlot::Generate(synth_param, None)); } - DelegationGenerics { data: slots, pos: GenericsPosition::Child, trait_impl: false } + DelegationGenerics { data: slots, pos: GenericsPosition::Child, trait_impl } } else { - DelegationGenerics::generate_all(sig_params, GenericsPosition::Child, false) + DelegationGenerics::generate_all(sig_child_params, GenericsPosition::Child, trait_impl) }; GenericsGenerationResults { parent: GenericsGenerationResult::new(parent_generics), child: GenericsGenerationResult::new(child_generics), - self_ty_propagation_kind: match free_to_trait_delegation { - true => Some(match qself_is_none { + self_ty_propagation_kind: match res.free_to_trait_delegation { + true => Some(match res.qself_is_none { true => hir::DelegationSelfTyPropagationKind::SelfParam, - false => match qself_is_infer { + false => match res.qself_is_infer { true => hir::DelegationSelfTyPropagationKind::SelfParam, // HirId is filled during generic args propagation. false => hir::DelegationSelfTyPropagationKind::SelfTy(HirId::INVALID), @@ -403,7 +447,7 @@ impl<'hir> LoweringContext<'_, 'hir> { /// infers than generic params then we will not process all infers thus not generating /// more generic params then needed (anyway it is an error). fn create_slots_from_args( - &self, + tcx: TyCtxt<'_>, args: &AngleBracketedArgs, params: &'hir [ty::GenericParamDef], add_first_self: bool, @@ -444,11 +488,7 @@ impl<'hir> LoweringContext<'_, 'hir> { (kw::Underscore, kw::UnderscoreLifetime) }; - self.tcx.dcx().emit_err(DelegationInfersMismatch { - span: arg.span(), - actual, - expected, - }); + tcx.dcx().emit_err(DelegationInfersMismatch { span: arg.span(), actual, expected }); } slots.push(match is_infer { @@ -459,7 +499,42 @@ impl<'hir> LoweringContext<'_, 'hir> { slots } +} + +impl<'hir> GenericsGenerationResults<'hir> { + pub(super) fn all_params(&self) -> impl Iterator> { + let parent = self.parent.generics.hir_generics_or_empty().params; + let child = self.child.generics.hir_generics_or_empty().params; + + // Order generics, first we have parent and child lifetimes, + // then parent and child types and consts. + // `generics_of` in `rustc_hir_analysis` will order them anyway, + // however we want the order to be consistent in HIR too. + parent + .iter() + .filter(|p| p.is_lifetime()) + .chain(child.iter().filter(|p| p.is_lifetime())) + .chain(parent.iter().filter(|p| !p.is_lifetime())) + .chain(child.iter().filter(|p| !p.is_lifetime())) + .copied() + } + + /// As we add hack predicates(`'a: 'a`) for all lifetimes (see `uplift_delegation_generic_params` + /// and `generate_lifetime_predicate` functions) we need to add them to delegation generics. + /// Those predicates will not affect resulting predicate inheritance and folding + /// in `rustc_hir_analysis`, as we inherit all predicates from delegation signature. + pub(super) fn all_predicates(&self) -> impl Iterator> { + self.parent + .generics + .hir_generics_or_empty() + .predicates + .into_iter() + .chain(self.child.generics.hir_generics_or_empty().predicates) + .copied() + } +} +impl<'hir> LoweringContext<'_, 'hir> { fn uplift_delegation_generic_params( &mut self, span: Span, diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation/mod.rs similarity index 58% rename from compiler/rustc_ast_lowering/src/delegation.rs rename to compiler/rustc_ast_lowering/src/delegation/mod.rs index a2a7b542258d1..cd742a6c30671 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation/mod.rs @@ -37,36 +37,30 @@ //! also be emitted during HIR ty lowering. use std::iter; -use std::ops::ControlFlow; use ast::visit::Visitor; -use hir::def::{DefKind, Res}; -use hir::{BodyId, HirId}; +use generics::GenericsGenerationResult; +use hir::HirId; +use hir::def::Res; use rustc_abi::ExternAbi; use rustc_ast as ast; -use rustc_ast::node_id::NodeMap; use rustc_ast::*; -use rustc_data_structures::fx::FxHashSet; -use rustc_hir::attrs::{AttributeKind, InlineAttr}; use rustc_hir::{self as hir, FnDeclFlags}; -use rustc_middle::span_bug; -use rustc_middle::ty::{Asyncness, PerOwnerResolverData}; -use rustc_span::def_id::{DefId, LocalDefId}; +use rustc_middle::ty::Asyncness; +use rustc_span::def_id::DefId; use rustc_span::symbol::kw; -use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym}; +use rustc_span::{Ident, Span, Symbol, sym}; -use crate::delegation::generics::{ - GenericsGenerationResult, GenericsGenerationResults, GenericsPosition, -}; -use crate::diagnostics::{ - CycleInDelegationSignatureResolution, DelegationAttemptedBlockWithDefsDeletion, - DelegationBlockSpecifiedWhenNoParams, UnresolvedDelegationCallee, -}; +use crate::delegation::generics::{GenericsGenerationResults, GenericsPosition}; +use crate::delegation::resolution::resolver::DelegationResolver; +use crate::delegation::resolution::{DelegationResolution, ParamInfo}; use crate::{ AllowReturnTypeNotation, ImplTraitContext, ImplTraitPosition, LoweringContext, ParamMode, }; +mod attributes; mod generics; +mod resolution; pub(crate) struct DelegationResults<'hir> { pub body_id: hir::BodyId, @@ -75,149 +69,24 @@ pub(crate) struct DelegationResults<'hir> { pub generics: &'hir hir::Generics<'hir>, } -struct AttrAdditionInfo { - pub equals: fn(&hir::Attribute) -> bool, - pub kind: AttrAdditionKind, -} - -enum AttrAdditionKind { - Default { factory: fn(Span) -> hir::Attribute }, - Inherit { factory: fn(Span, &hir::Attribute) -> hir::Attribute }, -} - -/// Summary info about function parameters. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -struct ParamInfo { - /// The number of function parameters, including any C variadic `...` parameter. - pub param_count: usize, - - /// Whether the function arguments end in a C variadic `...` parameter. - pub c_variadic: bool, - - /// The index of the splatted parameter, if any. - pub splatted: Option, -} - -const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; - -static ATTRS_ADDITIONS: &[AttrAdditionInfo] = &[ - AttrAdditionInfo { - equals: |a| matches!(a, hir::Attribute::Parsed(AttributeKind::MustUse { .. })), - kind: AttrAdditionKind::Inherit { - factory: |span, original_attr| { - let reason = match original_attr { - hir::Attribute::Parsed(AttributeKind::MustUse { reason, .. }) => *reason, - _ => None, - }; - - hir::Attribute::Parsed(AttributeKind::MustUse { span, reason }) - }, - }, - }, - AttrAdditionInfo { - equals: |a| matches!(a, hir::Attribute::Parsed(AttributeKind::Inline(..))), - kind: AttrAdditionKind::Default { - factory: |span| hir::Attribute::Parsed(AttributeKind::Inline(InlineAttr::Hint, span)), - }, - }, -]; - impl<'hir> LoweringContext<'_, 'hir> { - fn is_method(&self, def_id: DefId, span: Span) -> bool { - match self.tcx.def_kind(def_id) { - DefKind::Fn => false, - DefKind::AssocFn => self.tcx.associated_item(def_id).is_method(), - _ => span_bug!(span, "unexpected DefKind for delegation item"), - } - } - - fn check_for_cycles(&self, mut def_id: DefId, span: Span) -> Result<(), ErrorGuaranteed> { - let mut visited: FxHashSet = Default::default(); - - loop { - visited.insert(def_id); - - // If def_id is in local crate and it corresponds to another delegation - // it means that we refer to another delegation as a callee, so in order to obtain - // a signature DefId we obtain NodeId of the callee delegation and try to get signature from it. - if let Some(local_id) = def_id.as_local() - && let Some(info) = self.tcx.resolutions(()).delegation_infos.get(&local_id) - && let Ok(id) = info.resolution_id - { - def_id = id; - if visited.contains(&def_id) { - return Err(match visited.len() { - 1 => self.dcx().emit_err(UnresolvedDelegationCallee { span }), - _ => self.dcx().emit_err(CycleInDelegationSignatureResolution { span }), - }); - } - } else { - return Ok(()); - } - } - } - - pub(crate) fn lower_delegation( - &mut self, - delegation: &Delegation, - item_id: NodeId, - ) -> DelegationResults<'hir> { + pub(crate) fn lower_delegation(&mut self, delegation: &Delegation) -> DelegationResults<'hir> { let span = self.lower_span(delegation.last_segment_span()); - let Some(info) = self.tcx.resolutions(()).delegation_infos.get(&self.owner.def_id) else { - self.dcx().span_delayed_bug( - span, - format!("delegation resolution record was not found for {:?}", self.owner.def_id), - ); - - return self.generate_delegation_error(span, delegation); - }; - - let sig_id = info.resolution_id.and_then(|id| self.check_for_cycles(id, span).map(|_| id)); - - // Delegation can be missing from the `delegations_resolutions` table - // in illegal places such as function bodies in extern blocks (see #151356). - let Ok(sig_id) = sig_id else { - self.dcx().span_delayed_bug( - span, - format!("LoweringContext: the delegation {:?} is unresolved", item_id), - ); - + let resolver = DelegationResolver::new(self); + let Ok((res, mut generics)) = resolver.resolve_delegation(delegation, span) else { return self.generate_delegation_error(span, delegation); }; - self.add_attrs_if_needed(span, sig_id); + self.add_attrs_if_needed(&res); - let is_method = self.is_method(sig_id, span); + let (body_id, call_expr_id, unused_target_expr) = + self.lower_delegation_body(delegation, &res, &mut generics); - let param_info = self.param_info(sig_id); + let decl = self.lower_delegation_decl(&res, &generics, call_expr_id, unused_target_expr); - if !self.check_block_soundness(delegation, sig_id, is_method, param_info.param_count) { - return self.generate_delegation_error(span, delegation); - } + let sig = self.lower_delegation_sig(res.sig_id, decl, span); - let mut generics = self.uplift_delegation_generics(delegation, sig_id); - - let (body_id, call_expr_id, unused_target_expr) = self.lower_delegation_body( - delegation, - sig_id, - param_info.param_count, - &mut generics, - span, - ); - - let decl = self.lower_delegation_decl( - delegation.source, - sig_id, - param_info, - span, - &generics, - delegation.id, - call_expr_id, - unused_target_expr, - ); - - let sig = self.lower_delegation_sig(sig_id, decl, span); let ident = self.lower_ident(delegation.ident); let generics = self.arena.alloc(hir::Generics { @@ -231,160 +100,15 @@ impl<'hir> LoweringContext<'_, 'hir> { DelegationResults { body_id, sig, ident, generics } } - fn check_block_soundness( - &self, - delegation: &Delegation, - sig_id: DefId, - is_method: bool, - param_count: usize, - ) -> bool { - let Some(block) = delegation.body.as_ref() else { return true }; - let should_generate_block = self.should_generate_block(delegation, sig_id, is_method); - - // Report an error if user has explicitly specified delegation's target expression - // in a single delegation when reused function has no params. - if param_count == 0 && should_generate_block { - self.dcx().emit_err(DelegationBlockSpecifiedWhenNoParams { span: block.span }); - return false; - } - - struct DefinitionsFinder<'a> { - all_owners: &'a NodeMap>, - // `self.owner.node_id_to_def_id` - nested_def_ids: &'a NodeMap, - } - - impl<'a> ast::visit::Visitor<'a> for DefinitionsFinder<'a> { - type Result = ControlFlow<()>; - - fn visit_id(&mut self, id: NodeId) -> Self::Result { - /* - (from `tests\ui\delegation\target-expr-removal-defs-inside.rs`): - ```rust - reuse impl Trait for S1 { - some::path::<{ fn foo() {} }>::xd(); - fn foo() {} - self.0 - } - ``` - - Constant from unresolved path will be in `nested_owners`, - `fn foo() {}` will not be in `nested_owners` but will be in `owners`, - both have `LocalDefId`, so we check those two maps. - */ - match self.all_owners.contains_key(&id) || self.nested_def_ids.contains_key(&id) { - true => ControlFlow::Break(()), - false => ControlFlow::Continue(()), - } - } - } - - let mut collector = DefinitionsFinder { - all_owners: &self.resolver.owners, - nested_def_ids: &self.owner.node_id_to_def_id, - }; - - let contains_defs = collector.visit_block(block).is_break(); - - // If there are definitions inside and we can't delete target expression, so report an error. - // FIXME(fn_delegation): support deletion of target expression with defs inside. - if !should_generate_block && contains_defs { - self.dcx().emit_err(DelegationAttemptedBlockWithDefsDeletion { span: block.span }); - return false; - } - - true - } - - fn should_generate_block( - &self, - delegation: &Delegation, - sig_id: DefId, - is_method: bool, - ) -> bool { - is_method - || matches!(self.tcx.def_kind(sig_id), DefKind::Fn) - || matches!(delegation.source, DelegationSource::Single) - } - - fn add_attrs_if_needed(&mut self, span: Span, sig_id: DefId) { - let new_attrs = - self.create_new_attrs(ATTRS_ADDITIONS, span, sig_id, self.attrs.get(&PARENT_ID)); - - if new_attrs.is_empty() { - return; - } - - let new_arena_allocated_attrs = match self.attrs.get(&PARENT_ID) { - Some(existing_attrs) => self.arena.alloc_from_iter( - existing_attrs.iter().map(|a| a.clone()).chain(new_attrs.into_iter()), - ), - None => self.arena.alloc_from_iter(new_attrs.into_iter()), - }; - - self.attrs.insert(PARENT_ID, new_arena_allocated_attrs); - } - - fn create_new_attrs( - &self, - candidate_additions: &[AttrAdditionInfo], - span: Span, - sig_id: DefId, - existing_attrs: Option<&&[hir::Attribute]>, - ) -> Vec { - candidate_additions - .iter() - .filter_map(|addition_info| { - if let Some(existing_attrs) = existing_attrs - && existing_attrs - .iter() - .any(|existing_attr| (addition_info.equals)(existing_attr)) - { - return None; - } - - match addition_info.kind { - AttrAdditionKind::Default { factory } => Some(factory(span)), - AttrAdditionKind::Inherit { factory, .. } => - { - #[allow(deprecated)] - self.tcx - .get_all_attrs(sig_id) - .iter() - .find_map(|a| (addition_info.equals)(a).then(|| factory(span, a))) - } - } - }) - .collect::>() - } - - fn get_resolution_id(&self, node_id: NodeId) -> Option { - self.get_partial_res(node_id).and_then(|r| r.expect_full_res().opt_def_id()) - } - - /// Returns function parameter info, including C variadic `...` and `#[splat]` if present. - fn param_info(&self, def_id: DefId) -> ParamInfo { - let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder(); - - ParamInfo { - param_count: sig.inputs().len() + usize::from(sig.c_variadic()), - c_variadic: sig.c_variadic(), - splatted: sig.splatted(), - } - } - fn lower_delegation_decl( &mut self, - source: DelegationSource, - sig_id: DefId, - param_info: ParamInfo, - span: Span, + res: &DelegationResolution, generics: &GenericsGenerationResults<'hir>, - call_path_node_id: NodeId, call_expr_id: HirId, unused_target_expr: bool, ) -> &'hir hir::FnDecl<'hir> { - let ParamInfo { param_count, c_variadic, splatted } = param_info; + let &DelegationResolution { source, call_path_res, span, sig_id, .. } = res; + let ParamInfo { param_count, c_variadic, splatted } = res.param_info; // The last parameter in C variadic functions is skipped in the signature, // like during regular lowering. @@ -404,7 +128,7 @@ impl<'hir> LoweringContext<'_, 'hir> { sig_id, hir::InferDelegationSig::Output(self.arena.alloc(hir::DelegationInfo { call_expr_id, - call_path_res: self.get_resolution_id(call_path_node_id), + call_path_res, child_seg_id: generics.child.args_segment_id, child_seg_id_for_sig: generics.child.segment_id_for_sig(), parent_seg_id_for_sig: generics.parent.segment_id_for_sig(), @@ -517,23 +241,24 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_delegation_body( &mut self, delegation: &Delegation, - sig_id: DefId, - param_count: usize, + res: &DelegationResolution, generics: &mut GenericsGenerationResults<'hir>, - span: Span, - ) -> (BodyId, HirId, bool) { + ) -> (hir::BodyId, HirId, bool) { let block = delegation.body.as_deref(); let mut call_expr_id = HirId::INVALID; let mut unused_target_expr = false; let block_id = self.lower_body(|this| { + let &DelegationResolution { + param_info, span, should_generate_block, is_method, .. + } = res; + + let ParamInfo { param_count, .. } = param_info; + let mut parameters: Vec> = Vec::with_capacity(param_count); let mut args: Vec> = Vec::with_capacity(param_count); let mut stmts: &[hir::Stmt<'hir>] = &[]; - let is_method = this.is_method(sig_id, span); - let should_generate_block = this.should_generate_block(delegation, sig_id, is_method); - // Consider non-specified target expression as generated, // as we do not want to emit error when target expression is // not specified. @@ -580,7 +305,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } let (final_expr, hir_id) = - this.finalize_body_lowering(delegation, stmts, args, generics, span); + this.finalize_body_lowering(delegation, stmts, args, res, generics, span); call_expr_id = hir_id; @@ -597,6 +322,7 @@ impl<'hir> LoweringContext<'_, 'hir> { delegation: &Delegation, stmts: &'hir [hir::Stmt<'hir>], args: Vec>, + res: &DelegationResolution, generics: &mut GenericsGenerationResults<'hir>, span: Span, ) -> (hir::Expr<'hir>, HirId) { @@ -666,7 +392,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let args = self.arena.alloc_from_iter(args); let call = self.mk_expr(hir::ExprKind::Call(callee_path, args), span); - let expr = if let Some((parent, of_trait)) = self.should_wrap_return_value(delegation) { + let expr = if let Some((parent, of_trait)) = res.output_self_mapping { let res = Res::SelfTyAlias { alias_to: parent.to_def_id(), is_trait_impl: of_trait }; let ident = Ident::new(kw::SelfUpper, span); let path = self.create_resolved_path(res, ident, span); @@ -701,45 +427,6 @@ impl<'hir> LoweringContext<'_, 'hir> { (self.mk_expr(hir::ExprKind::Block(block, None), span), call.hir_id) } - fn should_wrap_return_value(&self, delegation: &Delegation) -> Option<(LocalDefId, bool)> { - // Heuristic: don't do wrapping if there is no target expression. - if delegation.body.is_none() { - return None; - } - - let tcx = self.tcx; - let parent = tcx.local_parent(self.owner.def_id); - let parent_kind = tcx.def_kind(parent); - - // Apply wrapping for delegations inside - // 1) Trait impls, as the return type of both signature function - // and generated delegation has `Self` generic param returned - // (checked below). - // FIXME(fn_delegation): think of enabling wrapping in more scenarios: - // trait-(impl)-to-free - // trait-(impl)-to-inherent - // inherent-to-free - // 2) Inherent methods when delegating to trait, as we change the type of - // `Self` to type of struct or enum we delegate from. - if !matches!(tcx.def_kind(parent), DefKind::Impl { .. }) { - return None; - } - - let is_trait_impl = parent_kind == DefKind::Impl { of_trait: true }; - - // Check that delegation path resolves to a trait AssocFn, not to a free method. - Some((parent, is_trait_impl)).filter(|_| { - self.get_resolution_id(delegation.id).is_some_and(|id| { - tcx.def_kind(id) == DefKind::AssocFn - // Check that the return type of the callee is `Self` param. - // After previous check we are sure that `sig_id` and `delegation.id` - // point to the same function. - && tcx.def_kind(tcx.parent(id)) == DefKind::Trait - && tcx.fn_sig(id).skip_binder().output().skip_binder().is_param(0) - }) - }) - } - fn process_segment( &mut self, span: Span, @@ -808,7 +495,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ); let callee_path = this.arena.alloc(this.mk_expr(hir::ExprKind::Path(path), span)); - let args = if let Some(block) = delegation.body.as_ref() { + let args = if let Some(block) = &delegation.body { this.arena.alloc_slice(&[this.lower_block_expr(block)]) } else { &mut [] diff --git a/compiler/rustc_ast_lowering/src/delegation/resolution.rs b/compiler/rustc_ast_lowering/src/delegation/resolution.rs new file mode 100644 index 0000000000000..3cb40d256ba8b --- /dev/null +++ b/compiler/rustc_ast_lowering/src/delegation/resolution.rs @@ -0,0 +1,264 @@ +use std::ops::ControlFlow; + +use ast::visit::Visitor; +use hir::def::DefKind; +use rustc_ast as ast; +use rustc_ast::*; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; +use rustc_middle::span_bug; +use rustc_span::def_id::{DefId, LocalDefId}; +use rustc_span::{ErrorGuaranteed, Span}; + +use crate::delegation::generics::GenericsGenerationResults; +use crate::delegation::resolution::resolver::DelegationResolver; +use crate::diagnostics::{ + CycleInDelegationSignatureResolution, DelegationAttemptedBlockWithDefsDeletion, + DelegationBlockSpecifiedWhenNoParams, UnresolvedDelegationCallee, +}; + +/// Summary info about function parameters. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(super) struct ParamInfo { + /// The number of function parameters, including any C variadic `...` parameter. + pub param_count: usize, + + /// Whether the function arguments end in a C variadic `...` parameter. + pub c_variadic: bool, + + /// The index of the splatted parameter, if any. + pub splatted: Option, +} + +pub(super) struct DelegationResolution { + pub sig_id: DefId, + pub is_method: bool, + pub param_info: ParamInfo, + pub span: Span, + pub should_generate_block: bool, + pub call_path_res: Option, + pub source: DelegationSource, + pub output_self_mapping: Option<(LocalDefId, bool)>, +} + +pub(super) mod resolver { + use rustc_ast::NodeId; + use rustc_hir::def_id::{DefId, LocalDefId}; + use rustc_middle::ty::TyCtxt; + + use crate::LoweringContext; + + /// Abstracts operations that are needed for delegation's resolution, so resolution + /// is independent of `LoweringContext`. Placed in a separate module so `LoweringContext` + /// can not be accessed directly. + pub(crate) struct DelegationResolver<'a, 'hir>(&'a LoweringContext<'a, 'hir>); + + impl<'a, 'tcx> DelegationResolver<'a, 'tcx> { + pub(crate) fn new(ctx: &'a LoweringContext<'a, 'tcx>) -> Self { + DelegationResolver(ctx) + } + + #[inline] + pub(crate) fn tcx(&self) -> TyCtxt<'tcx> { + self.0.tcx + } + + #[inline] + pub(crate) fn owner_id(&self) -> LocalDefId { + self.0.owner.def_id + } + + /// (from `tests\ui\delegation\target-expr-removal-defs-inside.rs`): + /// ```rust + /// reuse impl Trait for S1 { + /// some::path::<{ fn foo() {} }>::xd(); + /// fn foo() {} + /// self.0 + /// } + /// ``` + /// + /// Constant from unresolved path will be in `node_id_to_def_id`, + /// `fn foo() {}` will not be in `node_id_to_def_id` but will be in `owners`, + /// both have `LocalDefId`, so we check those two maps. + #[inline] + pub(crate) fn is_definition(&self, id: NodeId) -> bool { + self.0.resolver.owners.contains_key(&id) + || self.0.owner.node_id_to_def_id.contains_key(&id) + } + + #[inline] + pub(crate) fn get_resolution_id(&self, id: NodeId) -> Option { + self.0.get_partial_res(id).and_then(|r| r.expect_full_res().opt_def_id()) + } + } +} + +impl<'tcx> DelegationResolver<'_, 'tcx> { + pub(super) fn resolve_delegation( + &self, + delegation: &Delegation, + span: Span, + ) -> Result<(DelegationResolution, GenericsGenerationResults<'tcx>), ErrorGuaranteed> { + let tcx = self.tcx(); + let def_id = self.owner_id(); + + // Delegation can be missing from the `delegations_resolutions` table + // in illegal places such as function bodies in extern blocks (see #151356). + let sig_id = tcx + .resolutions(()) + .delegation_infos + .get(&def_id) + .map(|info| { + info.resolution_id.and_then(|id| self.check_for_cycles(id, span).map(|_| id)) + }) + .unwrap_or_else(|| { + Err(tcx.dcx().span_delayed_bug( + span, + format!("delegation resolution record was not found for {:?}", def_id), + )) + })?; + + let is_method = match tcx.def_kind(sig_id) { + DefKind::Fn => false, + DefKind::AssocFn => tcx.associated_item(sig_id).is_method(), + _ => span_bug!(span, "unexpected DefKind for delegation item"), + }; + + let sig = tcx.fn_sig(sig_id).skip_binder().skip_binder(); + let param_count = sig.inputs().len() + usize::from(sig.c_variadic()); + + let res = DelegationResolution { + is_method, + span, + sig_id, + // FIXME(splat): use `sig.splatted()` once FnSig has it + param_info: ParamInfo { param_count, c_variadic: sig.c_variadic(), splatted: None }, + should_generate_block: self.check_block_soundness( + delegation, + sig_id, + is_method, + param_count, + )?, + source: delegation.source, + call_path_res: self.get_resolution_id(delegation.id), + output_self_mapping: self.should_map_return_value(delegation), + }; + + Ok((res, self.resolve_and_generate_generics(delegation, sig_id))) + } + + fn check_for_cycles(&self, mut def_id: DefId, span: Span) -> Result<(), ErrorGuaranteed> { + let tcx = self.tcx(); + let mut visited: FxHashSet = Default::default(); + + loop { + visited.insert(def_id); + + // If def_id is in local crate and it corresponds to another delegation + // it means that we refer to another delegation as a callee, so in order to obtain + // a signature DefId we obtain NodeId of the callee delegation and try to get signature from it. + if let Some(local_id) = def_id.as_local() + && let Some(info) = tcx.resolutions(()).delegation_infos.get(&local_id) + && let Ok(id) = info.resolution_id + { + def_id = id; + if visited.contains(&def_id) { + return Err(match visited.len() { + 1 => tcx.dcx().emit_err(UnresolvedDelegationCallee { span }), + _ => tcx.dcx().emit_err(CycleInDelegationSignatureResolution { span }), + }); + } + } else { + return Ok(()); + } + } + } + + fn check_block_soundness( + &self, + delegation: &Delegation, + sig_id: DefId, + is_method: bool, + param_count: usize, + ) -> Result { + let tcx = self.tcx(); + let should_generate_block = is_method + || matches!(tcx.def_kind(sig_id), DefKind::Fn) + || matches!(delegation.source, DelegationSource::Single); + + let Some(block) = &delegation.body else { return Ok(should_generate_block) }; + + // Report an error if user has explicitly specified delegation's target expression + // in a single delegation when reused function has no params. + if param_count == 0 && should_generate_block { + let err = DelegationBlockSpecifiedWhenNoParams { span: block.span }; + return Err(tcx.dcx().emit_err(err)); + } + + struct DefinitionsFinder<'a, 'hir> { + ctx: &'a DelegationResolver<'a, 'hir>, + } + + impl<'a> Visitor<'a> for DefinitionsFinder<'a, '_> { + type Result = ControlFlow<()>; + + fn visit_id(&mut self, id: NodeId) -> Self::Result { + match self.ctx.is_definition(id) { + true => ControlFlow::Break(()), + false => ControlFlow::Continue(()), + } + } + } + + let mut collector = DefinitionsFinder { ctx: self }; + + let contains_defs = collector.visit_block(block).is_break(); + + // If there are definitions inside and we can't delete target expression, then report an error. + // FIXME(fn_delegation): support deletion of target expression with defs inside. + if should_generate_block || !contains_defs { + Ok(should_generate_block) + } else { + Err(tcx.dcx().emit_err(DelegationAttemptedBlockWithDefsDeletion { span: block.span })) + } + } + + fn should_map_return_value(&self, delegation: &Delegation) -> Option<(LocalDefId, bool)> { + // Heuristic: don't do wrapping if there is no target expression. + if delegation.body.is_none() { + return None; + } + + let tcx = self.tcx(); + let parent = tcx.local_parent(self.owner_id()); + let parent_kind = tcx.def_kind(parent); + + // Apply wrapping for delegations inside + // 1) Trait impls, as the return type of both signature function + // and generated delegation has `Self` generic param returned + // (checked below). + // FIXME(fn_delegation): think of enabling wrapping in more scenarios: + // trait-(impl)-to-free + // trait-(impl)-to-inherent + // inherent-to-free + // 2) Inherent methods when delegating to trait, as we change the type of + // `Self` to type of struct or enum we delegate from. + if !matches!(tcx.def_kind(parent), DefKind::Impl { .. }) { + return None; + } + + let is_trait_impl = parent_kind == DefKind::Impl { of_trait: true }; + + // Check that delegation path resolves to a trait AssocFn, not to a free method. + Some((parent, is_trait_impl)).filter(|_| { + self.get_resolution_id(delegation.id).is_some_and(|id| { + tcx.def_kind(id) == DefKind::AssocFn + // Check that the return type of the callee is `Self` param. + // After previous check we are sure that `sig_id` and `delegation.id` + // point to the same function. + && tcx.def_kind(tcx.parent(id)) == DefKind::Trait + && tcx.fn_sig(id).skip_binder().output().skip_binder().is_param(0) + }) + }) + } +} diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index c54777d98070a..8ebecb222b940 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -567,7 +567,7 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ItemKind::Macro(ident, macro_def, macro_kinds) } ItemKind::Delegation(delegation) => { - let delegation_results = self.lower_delegation(delegation, id); + let delegation_results = self.lower_delegation(delegation); hir::ItemKind::Fn { sig: delegation_results.sig, ident: delegation_results.ident, @@ -1052,7 +1052,7 @@ impl<'hir> LoweringContext<'_, 'hir> { (*ident, generics, kind, ty.is_some()) } AssocItemKind::Delegation(delegation) => { - let delegation_results = self.lower_delegation(delegation, i.id); + let delegation_results = self.lower_delegation(delegation); let item_kind = hir::TraitItemKind::Fn( delegation_results.sig, hir::TraitFn::Provided(delegation_results.body_id), @@ -1264,7 +1264,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) } AssocItemKind::Delegation(delegation) => { - let delegation_results = self.lower_delegation(delegation, i.id); + let delegation_results = self.lower_delegation(delegation); ( delegation.ident, ( From 841041c836af9b12e0731f2d8d3d9f0a8a09debc Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Thu, 2 Jul 2026 16:57:04 -0400 Subject: [PATCH 19/28] Optimize ArrayChunks::try_rfold with DoubleEndedIterator::next_chunk_back Co-authored-by: Vin --- library/core/src/iter/adapters/array_chunks.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/library/core/src/iter/adapters/array_chunks.rs b/library/core/src/iter/adapters/array_chunks.rs index a3e5ae5e7698b..6c502ee75e17f 100644 --- a/library/core/src/iter/adapters/array_chunks.rs +++ b/library/core/src/iter/adapters/array_chunks.rs @@ -1,8 +1,6 @@ use crate::array; use crate::iter::adapters::SourceIter; -use crate::iter::{ - ByRefSized, FusedIterator, InPlaceIterable, TrustedFused, TrustedRandomAccessNoCoerce, -}; +use crate::iter::{FusedIterator, InPlaceIterable, TrustedFused, TrustedRandomAccessNoCoerce}; use crate::num::NonZero; use crate::ops::{ControlFlow, NeverShortCircuit, Try}; @@ -128,15 +126,11 @@ where self.next_back_remainder(); let mut acc = init; - let mut iter = ByRefSized(&mut self.iter).rev(); // NB remainder is handled by `next_back_remainder`, so - // `next_chunk` can't return `Err` with non-empty remainder + // `next_chunk_back` can't return `Err` with non-empty remainder // (assuming correct `I as ExactSizeIterator` impl). - while let Ok(mut chunk) = iter.next_chunk() { - // FIXME: do not do double reverse - // (we could instead add `next_chunk_back` for example) - chunk.reverse(); + while let Ok(chunk) = self.iter.next_chunk_back() { acc = f(acc, chunk)? } From 8b3fbb7cf35047f4c24c5b648093870b08c77516 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 14 Jun 2026 19:28:55 +0000 Subject: [PATCH 20/28] Restrict access to DropKind. All call sites use a static `DropKind` value, and we already have a dedicated named method for lints. Switch to only use dedicated methods for consistency. --- .../src/builder/expr/as_rvalue.rs | 3 +- .../src/builder/expr/as_temp.rs | 6 +- .../src/builder/matches/mod.rs | 6 +- compiler/rustc_mir_build/src/builder/mod.rs | 5 +- compiler/rustc_mir_build/src/builder/scope.rs | 95 ++++++++----------- 5 files changed, 50 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index dd36411658305..ea484bd05878b 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -726,7 +726,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // This can be `None` if the expression's temporary scope was extended so that it can be // borrowed by a `const` or `static`. In that case, it's never dropped. if let Some(temp_lifetime) = temp_lifetime { - this.schedule_drop_storage_and_value(upvar_span, temp_lifetime, temp); + this.schedule_drop_storage(upvar_span, temp_lifetime, temp); + this.schedule_drop_value(upvar_span, temp_lifetime, temp); } block.and(Operand::Move(Place::from(temp))) diff --git a/compiler/rustc_mir_build/src/builder/expr/as_temp.rs b/compiler/rustc_mir_build/src/builder/expr/as_temp.rs index 55296c647c819..80d0e061487a7 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_temp.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_temp.rs @@ -7,7 +7,7 @@ use rustc_middle::mir::*; use rustc_middle::thir::*; use tracing::{debug, instrument}; -use crate::builder::scope::{DropKind, LintLevel}; +use crate::builder::scope::LintLevel; use crate::builder::{BlockAnd, BlockAndExtension, Builder}; impl<'a, 'tcx> Builder<'a, 'tcx> { @@ -120,7 +120,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // `bar(&foo())` or anything within a block will keep the // regular drops just like runtime code. if let Some(temp_lifetime) = temp_lifetime.temp_lifetime { - this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Storage); + this.schedule_drop_storage(expr_span, temp_lifetime, temp); } } } @@ -128,7 +128,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block = this.expr_into_dest(temp_place, block, expr_id).into_block(); if let Some(temp_lifetime) = temp_lifetime.temp_lifetime { - this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Value); + this.schedule_drop_value(expr_span, temp_lifetime, temp); } if let Some(backwards_incompatible) = temp_lifetime.backwards_incompatible { diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index f70416b2cf867..109f4de2698a4 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -28,7 +28,7 @@ use crate::builder::ForGuard::{self, OutsideGuard, RefWithinGuard}; use crate::builder::expr::as_place::PlaceBuilder; use crate::builder::matches::buckets::PartitionedCandidates; use crate::builder::matches::user_ty::ProjectedUserTypesNode; -use crate::builder::scope::{DropKind, LintLevel}; +use crate::builder::scope::LintLevel; use crate::builder::{ BlockAnd, BlockAndExtension, Builder, GuardFrame, GuardFrameLocal, LocalsForNode, }; @@ -791,7 +791,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) && matches!(schedule_drop, ScheduleDrops::Yes) { - self.schedule_drop(span, region_scope, local_id, DropKind::Storage); + self.schedule_drop_storage(span, region_scope, local_id); } let local_info = self.local_decls[local_id].local_info.as_mut().unwrap_crate_local(); if let LocalInfo::User(BindingForm::Var(var_info)) = &mut **local_info { @@ -808,7 +808,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ) { let local_id = self.var_local_id(var, for_guard); if let Some(region_scope) = self.region_scope_tree.var_scope(var.0.local_id) { - self.schedule_drop(span, region_scope, local_id, DropKind::Value); + self.schedule_drop_value(span, region_scope, local_id); } } diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 378e2e618fb93..b6478fc9c8deb 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -42,7 +42,7 @@ use rustc_middle::{bug, span_bug}; use rustc_span::{Span, Symbol}; use crate::builder::expr::as_place::PlaceBuilder; -use crate::builder::scope::{DropKind, LintLevel}; +use crate::builder::scope::LintLevel; pub(crate) fn closure_saved_names_of_captured_variables<'tcx>( tcx: TyCtxt<'tcx>, @@ -955,11 +955,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let place = Place::from(local); // Make sure we drop (parts of) the argument even when not matched on. - self.schedule_drop( + self.schedule_drop_value( param.pat.as_ref().map_or(expr_span, |pat| pat.span), argument_scope, local, - DropKind::Value, ); let Some(ref pat) = param.pat else { diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 84abb6ca70e70..53b84f1ea361f 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -162,7 +162,7 @@ struct DropData { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) enum DropKind { +enum DropKind { Value, Storage, ForLint, @@ -1390,47 +1390,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Scheduling drops // ================ - pub(crate) fn schedule_drop_storage_and_value( - &mut self, - span: Span, - region_scope: region::Scope, - local: Local, - ) { - self.schedule_drop(span, region_scope, local, DropKind::Storage); - self.schedule_drop(span, region_scope, local, DropKind::Value); - } - /// Indicates that `place` should be dropped on exit from `region_scope`. /// /// When called with `DropKind::Storage`, `place` shouldn't be the return /// place, or a function parameter. - pub(crate) fn schedule_drop( + fn schedule_drop( &mut self, span: Span, region_scope: region::Scope, local: Local, drop_kind: DropKind, ) { - let needs_drop = match drop_kind { - DropKind::Value | DropKind::ForLint => { - if !self.local_decls[local].ty.needs_drop(self.tcx, self.typing_env()) { - return; - } - true - } - DropKind::Storage => { - if local.index() <= self.arg_count { - span_bug!( - span, - "`schedule_drop` called with body argument {:?} \ - but its storage does not require a drop", - local, - ) - } - false - } - }; - // When building drops, we try to cache chains of drops to reduce the // number of `DropTree::add_drop` calls. This, however, means that // whenever we add a drop into a scope which already had some entries @@ -1477,7 +1447,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // path, we only need to invalidate the cache for drops that happen on // the unwind or coroutine drop paths. This means that for // non-coroutines we don't need to invalidate caches for `DropKind::Storage`. - let invalidate_caches = needs_drop || self.coroutine.is_some(); + let invalidate_caches = match drop_kind { + DropKind::Value | DropKind::ForLint => true, + DropKind::Storage => self.coroutine.is_some(), + }; for scope in self.scopes.scopes.iter_mut().rev() { if invalidate_caches { scope.invalidate_cache(); @@ -1501,6 +1474,39 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, local); } + /// Indicates that `place` should be marked `StorageDead` on exit from `region_scope`. + /// + /// `place` must not be the return place, or a function parameter. + pub(crate) fn schedule_drop_storage( + &mut self, + span: Span, + region_scope: region::Scope, + local: Local, + ) { + if local.index() <= self.arg_count { + span_bug!( + span, + "`schedule_drop` called with body argument {:?} \ + but its storage does not require a drop", + local, + ) + } + self.schedule_drop(span, region_scope, local, DropKind::Storage); + } + + /// Indicates that `place` should be dropped on exit from `region_scope`. + pub(crate) fn schedule_drop_value( + &mut self, + span: Span, + region_scope: region::Scope, + local: Local, + ) { + if !self.local_decls[local].ty.needs_drop(self.tcx, self.typing_env()) { + return; + } + self.schedule_drop(span, region_scope, local, DropKind::Value); + } + /// Schedule emission of a backwards incompatible drop lint hint. /// Applicable only to temporary values for now. #[instrument(level = "debug", skip(self))] @@ -1512,28 +1518,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ) { // Note that we are *not* gating BIDs here on whether they have significant destructor. // We need to know all of them so that we can capture potential borrow-checking errors. - for scope in self.scopes.scopes.iter_mut().rev() { - // Since we are inserting linting MIR statement, we have to invalidate the caches - scope.invalidate_cache(); - if scope.region_scope == region_scope { - let region_scope_span = region_scope.span(self.tcx, self.region_scope_tree); - let scope_end = self.tcx.sess.source_map().end_point(region_scope_span); - - scope.drops.push(DropData { - source_info: SourceInfo { span: scope_end, scope: scope.source_scope }, - local, - kind: DropKind::ForLint, - }); - - return; - } - } - span_bug!( - span, - "region scope {:?} not in scope to drop {:?} for linting", - region_scope, - local - ); + self.schedule_drop(span, region_scope, local, DropKind::ForLint); } /// Indicates that the "local operand" stored in `local` is From 28ed28af527adfcbe54a15c27df792833e35f423 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Wed, 17 Jun 2026 22:15:55 +0000 Subject: [PATCH 21/28] Simplify API. --- compiler/rustc_mir_build/src/builder/block.rs | 6 +-- compiler/rustc_mir_build/src/builder/scope.rs | 43 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/block.rs b/compiler/rustc_mir_build/src/builder/block.rs index 47d231603d33b..7d85579325751 100644 --- a/compiler/rustc_mir_build/src/builder/block.rs +++ b/compiler/rustc_mir_build/src/builder/block.rs @@ -184,7 +184,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Declare the bindings, which may create a source scope. let remainder_span = remainder_scope.span(this.tcx, this.region_scope_tree); - this.push_scope((*remainder_scope, source_info)); + this.push_scope(*remainder_scope); let_scope_stack.push(remainder_scope); let visibility_scope = @@ -242,7 +242,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { this.block_context.push(BlockFrame::Statement { ignores_expr_result }); // Enter the remainder scope, i.e., the bindings' destruction scope. - this.push_scope((*remainder_scope, source_info)); + this.push_scope(*remainder_scope); let_scope_stack.push(remainder_scope); // Declare the bindings, which may create a source scope. @@ -346,7 +346,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Finally, we pop all the let scopes before exiting out from the scope of block // itself. for scope in let_scope_stack.into_iter().rev() { - block = this.pop_scope((*scope, source_info), block).into_block(); + block = this.pop_scope(*scope, block).into_block(); } // Restore the original source scope. this.source_scope = outer_source_scope; diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 53b84f1ea361f..2197bbeab25cf 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -488,11 +488,11 @@ impl<'tcx> Scopes<'tcx> { } } - fn push_scope(&mut self, region_scope: (region::Scope, SourceInfo), vis_scope: SourceScope) { + fn push_scope(&mut self, region_scope: region::Scope, vis_scope: SourceScope) { debug!("push_scope({:?})", region_scope); self.scopes.push(Scope { source_scope: vis_scope, - region_scope: region_scope.0, + region_scope, drops: vec![], moved_locals: vec![], cached_unwind_block: None, @@ -500,13 +500,13 @@ impl<'tcx> Scopes<'tcx> { }); } - fn pop_scope(&mut self, region_scope: (region::Scope, SourceInfo)) -> Scope { + fn pop_scope(&mut self, region_scope: region::Scope) { let scope = self.scopes.pop().unwrap(); - assert_eq!(scope.region_scope, region_scope.0); - scope + assert_eq!(scope.region_scope, region_scope); } - fn scope_index(&self, region_scope: region::Scope, span: Span) -> usize { + /// Returns the position in the scope stack of `region_scope`. + fn stack_index(&self, region_scope: region::Scope, span: Span) -> usize { self.scopes .iter() .rposition(|scope| scope.region_scope == region_scope) @@ -681,7 +681,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { #[instrument(skip(self, f), level = "debug")] pub(crate) fn in_scope( &mut self, - region_scope: (region::Scope, SourceInfo), + (region_scope, source_info): (region::Scope, SourceInfo), lint_level: LintLevel, f: F, ) -> BlockAnd @@ -692,7 +692,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { if let LintLevel::Explicit(current_hir_id) = lint_level { let parent_id = self.source_scopes[source_scope].local_data.as_ref().unwrap_crate_local().lint_root; - self.maybe_new_source_scope(region_scope.1.span, current_hir_id, parent_id); + self.maybe_new_source_scope(source_info.span, current_hir_id, parent_id); } self.push_scope(region_scope); let mut block; @@ -721,7 +721,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// scope and call `pop_scope` afterwards. Note that these two /// calls must be paired; using `in_scope` as a convenience /// wrapper maybe preferable. - pub(crate) fn push_scope(&mut self, region_scope: (region::Scope, SourceInfo)) { + pub(crate) fn push_scope(&mut self, region_scope: region::Scope) { self.scopes.push_scope(region_scope, self.source_scope); } @@ -730,13 +730,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// This must match 1-to-1 with `push_scope`. pub(crate) fn pop_scope( &mut self, - region_scope: (region::Scope, SourceInfo), + region_scope: region::Scope, mut block: BasicBlock, ) -> BlockAnd<()> { debug!("pop_scope({:?}, {:?})", region_scope, block); block = self.leave_top_scope(block); - self.scopes.pop_scope(region_scope); block.unit() @@ -804,7 +803,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } let region_scope = self.scopes.breakable_scopes[break_index].region_scope; - let scope_index = self.scopes.scope_index(region_scope, span); + let stack_index = self.scopes.stack_index(region_scope, span); let drops = if destination.is_some() { &mut self.scopes.breakable_scopes[break_index].break_drops } else { @@ -822,7 +821,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }; let mut drop_idx = ROOT_NODE; - for scope in &self.scopes.scopes[scope_index + 1..] { + for scope in &self.scopes.scopes[stack_index + 1..] { for drop in &scope.drops { drop_idx = drops.add_drop(*drop, drop_idx); } @@ -1007,9 +1006,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.block_context.pop(); let discr = self.temp(discriminant_ty, source_info.span); - let scope_index = self + let stack_index = self .scopes - .scope_index(self.scopes.const_continuable_scopes[break_index].region_scope, span); + .stack_index(self.scopes.const_continuable_scopes[break_index].region_scope, span); let scope = &mut self.scopes.const_continuable_scopes[break_index]; self.cfg.push_assign(block, source_info, discr, rvalue); let drop_and_continue_block = self.cfg.start_new_block(); @@ -1022,7 +1021,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let drops = &mut scope.const_continue_drops; - let drop_idx = self.scopes.scopes[scope_index + 1..] + let drop_idx = self.scopes.scopes[stack_index + 1..] .iter() .flat_map(|scope| &scope.drops) .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); @@ -1032,10 +1031,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(imaginary_target, source_info, TerminatorKind::UnwindResume); let region_scope = scope.region_scope; - let scope_index = self.scopes.scope_index(region_scope, span); + let stack_index = self.scopes.stack_index(region_scope, span); let mut drops = DropTree::new(); - let drop_idx = self.scopes.scopes[scope_index + 1..] + let drop_idx = self.scopes.scopes[stack_index + 1..] .iter() .flat_map(|scope| &scope.drops) .fold(ROOT_NODE, |drop_idx, &drop| drops.add_drop(drop, drop_idx)); @@ -1066,14 +1065,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .unwrap_or_else(|| span_bug!(source_info.span, "no if-then scope found")); let target = if_then_scope.region_scope; - let scope_index = self.scopes.scope_index(target, source_info.span); + let stack_index = self.scopes.stack_index(target, source_info.span); // Upgrade `if_then_scope` to `&mut`. let if_then_scope = self.scopes.if_then_scope.as_mut().expect("upgrading & to &mut"); let mut drop_idx = ROOT_NODE; let drops = &mut if_then_scope.else_drops; - for scope in &self.scopes.scopes[scope_index + 1..] { + for scope in &self.scopes.scopes[stack_index + 1..] { for drop in &scope.drops { drop_idx = drops.add_drop(*drop, drop_idx); } @@ -1596,7 +1595,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// It is possible to unwind to some ancestor scope if some drop panics as /// the program breaks out of a if-then scope. fn diverge_cleanup_target(&mut self, target_scope: region::Scope, span: Span) -> DropIdx { - let target = self.scopes.scope_index(target_scope, span); + let target = self.scopes.stack_index(target_scope, span); let (uncached_scope, mut cached_drop) = self.scopes.scopes[..=target] .iter() .enumerate() @@ -1659,7 +1658,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.coroutine.is_some(), "diverge_dropline_target is valid only for coroutine" ); - let target = self.scopes.scope_index(target_scope, span); + let target = self.scopes.stack_index(target_scope, span); let (uncached_scope, mut cached_drop) = self.scopes.scopes[..=target] .iter() .enumerate() From 5e197949dae8cf22b948974b113c31ca85d09bf9 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 3 Jul 2026 10:08:38 +1000 Subject: [PATCH 22/28] Mark `SizeHint::size_hint` as a `final` method This enforces the invariant that `size_hint` should always return `(lower_bound(), upper_bound())`. --- library/core/src/io/size_hint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/io/size_hint.rs b/library/core/src/io/size_hint.rs index 8a5b0a63c075b..407da98d38b0e 100644 --- a/library/core/src/io/size_hint.rs +++ b/library/core/src/io/size_hint.rs @@ -5,7 +5,7 @@ pub trait SizeHint { fn upper_bound(&self) -> Option; - fn size_hint(&self) -> (usize, Option) { + final fn size_hint(&self) -> (usize, Option) { (self.lower_bound(), self.upper_bound()) } } From c2f51ae80799e4482e8015436ed8a81ff7b32222 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 3 Jul 2026 10:10:01 +1000 Subject: [PATCH 23/28] Add documentation to `SizeHint` Leaving the trait in a better state than it previously was. Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/core/src/io/size_hint.rs | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/library/core/src/io/size_hint.rs b/library/core/src/io/size_hint.rs index 407da98d38b0e..fa4e25c62f6d0 100644 --- a/library/core/src/io/size_hint.rs +++ b/library/core/src/io/size_hint.rs @@ -1,10 +1,45 @@ +/// Internal trait used to allow for specialization in `Read::size_hint` and +/// `Iterator::size_hint`. +/// +/// All types implement this through a blanket default implementation returning +/// a hint of `(0, None)`. +/// +/// Implementors should only provide [`lower_bound`](SizeHint::lower_bound) and +/// [`upper_bound`](SizeHint::upper_bound). +/// [`size_hint`](SizeHint::size_hint) is provided as a `final` method to enforce +/// correctness. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub trait SizeHint { + /// Returns a lower bound on the number of elements this container-like item + /// contains. + /// For example, an array `[u8; 12]` could return any value between `0` and + /// `12` inclusively as a correct implementation. + /// + /// Through specialization, all types implement this method returning a default + /// value of `0`. + /// + /// Implementations *must* ensure the returned value is less than or equal to + /// the true element count. fn lower_bound(&self) -> usize; + /// Returns an upper bound on the number of elements this container-like item + /// contains if it can be determined, otherwise `None`. + /// + /// Through specialization, all types implement this method returning a default + /// value of `None`. + /// + /// Implementations *must* ensure the returned value is greater than or equal + /// to the true element count. fn upper_bound(&self) -> Option; + /// Returns an estimate for the number of elements this container like type + /// contains. + /// + /// This is a `final` method, and is guaranteed to return + /// `(self.lower_bound(), self.upper_bound())`. + /// + /// Without specialization, types implementing this trait will return `(0, None)`. final fn size_hint(&self) -> (usize, Option) { (self.lower_bound(), self.upper_bound()) } From 4e2363a0935ee590599aeab4162fed51398ce251 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Thu, 2 Jul 2026 18:05:49 -0700 Subject: [PATCH 24/28] library: Comment on libtest's dicey internal soundness --- library/test/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index 791af5f8ee2a9..e4280520bd8ba 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -207,6 +207,14 @@ pub fn test_main_static_abort(tests: &[&TestDescAndFn]) { // If we're being run in SpawnedSecondary mode, run the test here. run_test // will then exit the process. if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) { + // SAFETY: Technically, this is a racy access that we probably shouldn't do? + // In practice, this is completely fine as long as the test harness is made of Rust, + // as std will synchronize the racy accesses that occur when they happen through std::env. + // Any unsoundness can only be exposed in practice if e.g. C code also takes an interest + // in these variables. + // + // If we ever grow an actual story for libtest and start documenting custom harness reqs, + // we should either fix this being racy or say "write it in Rust, please". unsafe { env::remove_var(SECONDARY_TEST_INVOKER_VAR); } @@ -214,6 +222,7 @@ pub fn test_main_static_abort(tests: &[&TestDescAndFn]) { // Convert benchmarks to tests if we're not benchmarking. let mut tests = tests.iter().map(make_owned_test).collect::>(); if env::var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR).is_ok() { + // SAFETY: Same as for SECONDARY_TEST_INVOKER_VAR unsafe { env::remove_var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR); } From 23e6ca02228bf929cd426fc66e58642b889bd45f Mon Sep 17 00:00:00 2001 From: Adwin White Date: Wed, 1 Jul 2026 17:35:25 +0800 Subject: [PATCH 25/28] refactor normalization in `coerce_shared_info` --- .../src/coherence/builtin.rs | 96 +++++++++---------- .../coerce-shared-mut-ref-field-validation.rs | 3 +- ...rce-shared-mut-ref-field-validation.stderr | 9 -- tests/ui/reborrow/coerce-shared-normalize.rs | 56 +++++++++++ 4 files changed, 105 insertions(+), 59 deletions(-) delete mode 100644 tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr create mode 100644 tests/ui/reborrow/coerce-shared-normalize.rs diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 1b174631aa890..4ff870b405e5a 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -10,7 +10,7 @@ use rustc_hir::ItemKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_infer::infer::{self, InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt}; -use rustc_infer::traits::{Obligation, PredicateObligations}; +use rustc_infer::traits::Obligation; use rustc_middle::ty::adjustment::CoerceUnsizedInfo; use rustc_middle::ty::print::PrintTraitRefExt as _; use rustc_middle::ty::relate::solver_relating::RelateExt; @@ -469,37 +469,6 @@ fn visit_implementation_of_dispatch_from_dyn(checker: &Checker<'_>) -> Result<() } } -fn structurally_normalize_ty<'tcx>( - tcx: TyCtxt<'tcx>, - infcx: &InferCtxt<'tcx>, - impl_did: LocalDefId, - span: Span, - ty: Unnormalized<'tcx, Ty<'tcx>>, -) -> Option<(Ty<'tcx>, PredicateObligations<'tcx>)> { - let ocx = ObligationCtxt::new(infcx); - let Ok(normalized_ty) = ocx.structurally_normalize_ty( - &traits::ObligationCause::misc(span, impl_did), - tcx.param_env(impl_did), - ty, - ) else { - // We shouldn't have errors here in the old solver, except for - // evaluate/fulfill mismatches, but that's not a reason for an ICE. - return None; - }; - let errors = ocx.try_evaluate_obligations(); - if !errors.is_empty() { - if infcx.next_trait_solver() { - unreachable!(); - } - // We shouldn't have errors here in the old solver, except for - // evaluate/fulfill mismatches, but that's not a reason for an ICE. - debug!(?errors, "encountered errors while fulfilling"); - return None; - } - - Some((normalized_ty, ocx.into_pending_obligations())) -} - pub(crate) fn reborrow_info<'tcx>( tcx: TyCtxt<'tcx>, impl_did: LocalDefId, @@ -552,8 +521,12 @@ pub(crate) fn reborrow_info<'tcx>( return Ok(()); } + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); // We've found some data fields. They must all be either be Copy or Reborrow. for (field, span) in data_fields { + let field = ocx + .deeply_normalize(&traits::ObligationCause::misc(span, impl_did), param_env, field) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; if assert_field_type_is_reborrow( tcx, &infcx, @@ -620,17 +593,16 @@ pub(crate) fn coerce_shared_info<'tcx>( } assert_eq!(trait_ref.def_id, coerce_shared_trait); - let Some((target, _obligations)) = structurally_normalize_ty( - tcx, - &infcx, - impl_did, - span, - Unnormalized::new_wip(trait_ref.args.type_at(1)), - ) else { - todo!("something went wrong with structurally_normalize_ty"); - }; - + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); let param_env = tcx.param_env(impl_did); + let (source, target) = ocx + .deeply_normalize( + &traits::ObligationCause::misc(span, impl_did), + param_env, + Unnormalized::new_wip((source, trait_ref.args.type_at(1))), + ) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; + assert!(!source.has_escaping_bound_vars()); let data = match (source.kind(), target.kind()) { @@ -672,6 +644,20 @@ pub(crate) fn coerce_shared_info<'tcx>( // them below. let (a, span_a) = a_data_fields[0]; let (b, span_b) = b_data_fields[0]; + let a = ocx + .deeply_normalize( + &traits::ObligationCause::misc(span_a, impl_did), + param_env, + a, + ) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; + let b = ocx + .deeply_normalize( + &traits::ObligationCause::misc(span_b, impl_did), + param_env, + b, + ) + .map_err(|errors| infcx.err_ctxt().report_fulfillment_errors(errors))?; Some((a, b, coerce_shared_trait, span_a, span_b)) } else { @@ -696,12 +682,19 @@ pub(crate) fn coerce_shared_info<'tcx>( // // 1 data field each; they must be the same type and Copy, or relate to one another using // CoerceShared. + // + // FIXME(reborrow): we should do the relating inside `probe` so the region constraint + // doesn't affect later result in case that this relating fails. + // We should resolve regions if the relating succeeds. + // Besides, the regions of `Ref`s are not checked here so `&'a mut T -> &'static T` is + // allowed. if source.ref_mutability() == Some(ty::Mutability::Mut) && target.ref_mutability() == Some(ty::Mutability::Not) && infcx - .eq_structurally_relating_aliases( + .relate( param_env, source.peel_refs(), + ty::Variance::Invariant, target.peel_refs(), source_field_span, ) @@ -710,14 +703,16 @@ pub(crate) fn coerce_shared_info<'tcx>( // &mut T implements CoerceShared to &T, except not really. return Ok(()); } + + // FIXME(reborrow): we should do the relating inside `probe` so the region constraint + // doesn't affect later result in case that this relating fails. if infcx - .eq_structurally_relating_aliases(param_env, source, target, source_field_span) + .relate(param_env, source, ty::Variance::Invariant, target, source_field_span) .is_err() { // The two data fields don't agree on a common type; this means // that they must be `A: CoerceShared`. Register an obligation // for that. - let ocx = ObligationCtxt::new_with_diagnostics(&infcx); let cause = traits::ObligationCause::misc(span, impl_did); let obligation = Obligation::new( tcx, @@ -735,6 +730,8 @@ pub(crate) fn coerce_shared_info<'tcx>( ocx.resolve_regions_and_report_errors(impl_did, param_env, [])?; } else { // Types match: check that it is Copy. + // + // FIXME(reborrow): We should resolve regions here. assert_field_type_is_copy(tcx, &infcx, impl_did, param_env, source, source_field_span)?; } } @@ -754,19 +751,20 @@ fn generic_lifetime_params_count(args: &[ty::GenericArg<'_>]) -> usize { args.iter().filter(|arg| arg.as_region().is_some()).count() } -// FIXME(#155345): This should return `Unnormalized` fn collect_struct_data_fields<'tcx>( tcx: TyCtxt<'tcx>, def: ty::AdtDef<'tcx>, args: ty::GenericArgsRef<'tcx>, -) -> Vec<(Ty<'tcx>, Span)> { +) -> Vec<(Unnormalized<'tcx, Ty<'tcx>>, Span)> { def.non_enum_variant() .fields .iter() .filter_map(|f| { // Ignore PhantomData fields - let ty = f.ty(tcx, args).skip_norm_wip(); - if ty.is_phantom_data() { + let ty = f.ty(tcx, args); + // FIXME(#155345): alias might be normalized to PhantomData. + // We probably should normalize here instead. + if ty.skip_norm_wip().is_phantom_data() { return None; } Some((ty, tcx.def_span(f.did))) diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs index 9d87ff0bb938a..a425ab5fd1ced 100644 --- a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs +++ b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.rs @@ -1,3 +1,5 @@ +//@ check-pass + #![feature(reborrow)] use std::marker::{CoerceShared, Reborrow}; @@ -25,7 +27,6 @@ struct AliasRef<'a> { } impl<'a> CoerceShared> for AliasMut<'a> {} -//~^ ERROR struct InnerLifetimeMut<'a> { value: &'a mut &'static (), diff --git a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr b/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr deleted file mode 100644 index 63a96d65e176d..0000000000000 --- a/tests/ui/reborrow/coerce-shared-mut-ref-field-validation.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0277]: the trait bound `&'a mut u32: CoerceShared<&'a u32>` is not satisfied - --> $DIR/coerce-shared-mut-ref-field-validation.rs:27:1 - | -LL | impl<'a> CoerceShared> for AliasMut<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `CoerceShared<&'a u32>` is not implemented for `&'a mut u32` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/reborrow/coerce-shared-normalize.rs b/tests/ui/reborrow/coerce-shared-normalize.rs new file mode 100644 index 0000000000000..a212eaf27fccf --- /dev/null +++ b/tests/ui/reborrow/coerce-shared-normalize.rs @@ -0,0 +1,56 @@ +//@ check-pass + +// Previously we didn't normalize the source type of `CoerceShared` when +// checking its trait impls. +// We also didn't normalize the types of fields in the wrappers. +// So they fail the trait impl check in `coerce_shared_info` even if +// the normalized types satisfy the requirements. + +#![feature(reborrow)] +use std::marker::{CoerceShared, Reborrow}; + +struct NonParam; + +trait HasAssoc<'a> { + type Assoc; +} + +struct A; +impl<'a> HasAssoc<'a> for A { + type Assoc = &'a mut NonParam; +} +type MutAlias<'a> = >::Assoc; + +struct B; +impl<'a> HasAssoc<'a> for B { + type Assoc = &'a NonParam; +} +type RefAlias<'a> = >::Assoc; + +struct CustomMut<'a>(MutAlias<'a>); +struct CustomRef<'a>(RefAlias<'a>); + +struct C; +impl<'a> HasAssoc<'a> for C { + type Assoc = CustomMut<'a>; +} +type CustomMutAlias<'a> = >::Assoc; + +struct D; +impl<'a> HasAssoc<'a> for D { + type Assoc = CustomRef<'a>; +} +type CustomRefAlias<'a> = >::Assoc; + +impl<'a> Clone for CustomRef<'a> { + fn clone(&self) -> Self { + Self(self.0) + } +} +impl<'a> Copy for CustomRef<'a> {} + + +impl<'a> Reborrow for CustomMut<'a> {} +impl<'a> CoerceShared> for CustomMutAlias<'a> {} + +fn main() {} From 4c3aa1f9a12f60b9f071f91f7b045ceb5db56dcd Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Thu, 2 Jul 2026 12:58:00 +0200 Subject: [PATCH 26/28] don't use `Finalize` when resolving visbilities during AST expansion --- compiler/rustc_resolve/src/build_reduced_graph.rs | 2 +- compiler/rustc_resolve/src/ident.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index bb9bf8976f672..c35dc02fb1de1 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -475,7 +475,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { } fn resolve_visibility(&mut self, vis: &ast::Visibility) -> Visibility { - match self.r.try_resolve_visibility(&self.parent_scope, vis, true) { + match self.r.try_resolve_visibility(&self.parent_scope, vis, false) { Ok(vis) => vis, Err(error) => { self.r.delayed_vis_resolution_errors.push(DelayedVisResolutionError { diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index d5babe2e94a90..6bf30a470cf39 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1154,6 +1154,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { resolution.as_ref().and_then(|r| r.non_glob_decl).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { + // finalize implies that the module is fully expanded + assert!(!module.has_unexpanded_invocations()); return self.get_mut().finalize_module_binding( ident, orig_ident_span, @@ -1218,6 +1220,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { resolution.as_ref().and_then(|r| r.glob_decl).filter(|b| Some(*b) != ignore_decl); if let Some(finalize) = finalize { + // finalize implies that the module is fully expanded + assert!(!module.has_unexpanded_invocations()); return self.get_mut().finalize_module_binding( ident, orig_ident_span, From c286fbb18d8a1407a221bae6e69f92d94db977a7 Mon Sep 17 00:00:00 2001 From: jyn Date: Fri, 3 Jul 2026 14:30:37 +0200 Subject: [PATCH 27/28] rip out TIDY_PRINT_DIFF and just always show diffs by default. not sure why you'd want to hide them. --- src/ci/docker/host-x86_64/tidy/Dockerfile | 3 +- src/etc/pre-push.sh | 3 +- src/tools/tidy/src/extra_checks/mod.rs | 37 +++++++++-------------- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/ci/docker/host-x86_64/tidy/Dockerfile b/src/ci/docker/host-x86_64/tidy/Dockerfile index e84d61e0b99cc..22c947a3ca7ef 100644 --- a/src/ci/docker/host-x86_64/tidy/Dockerfile +++ b/src/ci/docker/host-x86_64/tidy/Dockerfile @@ -39,5 +39,4 @@ COPY host-x86_64/pr-check-1/validate-toolstate.sh /scripts/ # NOTE: intentionally uses python2 for x.py so we can test it still works. # validate-toolstate only runs in our CI, so it's ok for it to only support python3. -ENV SCRIPT="TIDY_PRINT_DIFF=1 python2.7 ../x.py test \ - src/tools/tidy tidyselftest --extra-checks=py,cpp,js,spellcheck" +ENV SCRIPT="python2.7 ../x.py test src/tools/tidy tidyselftest --extra-checks=py,cpp,js,spellcheck" diff --git a/src/etc/pre-push.sh b/src/etc/pre-push.sh index 210edbf0cda66..fb2007db9f12c 100755 --- a/src/etc/pre-push.sh +++ b/src/etc/pre-push.sh @@ -33,8 +33,7 @@ ROOT_DIR="$(git rev-parse --show-toplevel)" echo "Running pre-push script $ROOT_DIR/x test tidy" cd "$ROOT_DIR" -# The env var is necessary for printing diffs in py (fmt/lint) and cpp. -TIDY_PRINT_DIFF=1 ./x test tidy \ +./x test tidy \ --set build.locked-deps=true \ --extra-checks auto:py,auto:cpp,auto:js if [ $? -ne 0 ]; then diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs index 75b02520a3d46..d358858b24598 100644 --- a/src/tools/tidy/src/extra_checks/mod.rs +++ b/src/tools/tidy/src/extra_checks/mod.rs @@ -10,12 +10,11 @@ //! configuration after a double dash (`--extra-checks=py -- foo.py`) //! 2. Build configuration based on args/environment: //! - Formatters by default are in check only mode -//! - If in CI (TIDY_PRINT_DIFF=1 is set), check and print the diff //! - If `--bless` is provided, formatters may run //! - Pass any additional config after the `--`. If no files are specified, //! use a default. -//! 3. Print the output of the given command. If it fails and `TIDY_PRINT_DIFF` -//! is set, rerun the tool to print a suggestion diff (for e.g. CI) +//! 3. Print the output of the given command. If it fails, rerun the tool to print a suggestion +//! diff. use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; @@ -206,12 +205,6 @@ fn show_bless_help(mode: &str, action: &str, bless: bool) { } } -fn show_diff() -> bool { - let disable_diff = - std::env::var("TIDY_PRINT_DIFF").is_ok_and(|v| v.eq_ignore_ascii_case("false") || v == "0"); - !disable_diff -} - fn check_spellcheck(root_path: &Path, outdir: &Path, cargo: &Path, tidy_ctx: &TidyCtx) { let mut check = tidy_ctx.start_check("extra_checks:spellcheck"); @@ -318,7 +311,7 @@ fn check_python_lint( let res = run_ruff(root_path, outdir, py_path, &cfg_args, &file_args, args); - if res.is_err() && !bless && show_diff() { + if res.is_err() && !bless { eprintln!("\npython linting failed! Printing diff suggestions:"); let diff_res = run_ruff( @@ -362,18 +355,16 @@ fn check_python_fmt( let res = run_ruff(root_path, outdir, py_path, &cfg_args, &file_args, &args); if res.is_err() && !bless { - if show_diff() { - eprintln!("\npython formatting does not match! Printing diff:"); - - let _ = run_ruff( - root_path, - outdir, - py_path, - &cfg_args, - &file_args, - &["format".as_ref(), "--diff".as_ref()], - ); - } + eprintln!("\npython formatting does not match! Printing diff:"); + + let _ = run_ruff( + root_path, + outdir, + py_path, + &cfg_args, + &file_args, + &["format".as_ref(), "--diff".as_ref()], + ); show_bless_help("py:fmt", "reformat Python code", bless); } @@ -425,7 +416,7 @@ fn check_cpp_fmt( let args = merge_args(&cfg_args_clang_format, &file_args_clang_format); let res = py_runner(py_path, false, None, "clang-format", &args); - if res.is_err() && !bless && show_diff() { + if res.is_err() && !bless { eprintln!("\nclang-format linting failed! Printing diff suggestions:"); let mut cfg_args_diff = cfg_args.clone(); From d6f62f661dc5ea1c6928fa234e3a5b686080bb37 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 3 Jul 2026 23:33:36 +0200 Subject: [PATCH 28/28] rustdoc: Fix crash when trying to inline foreign item which cannot have attributes --- src/librustdoc/clean/inline.rs | 37 +++++++++++-------- .../reexport/auxiliary/glob-import.rs | 1 + tests/rustdoc-html/reexport/glob-import.rs | 17 +++++++++ 3 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 tests/rustdoc-html/reexport/auxiliary/glob-import.rs create mode 100644 tests/rustdoc-html/reexport/glob-import.rs diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 9469c806a26ad..d4cdc7781fed5 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -681,21 +681,26 @@ fn build_module( // We are only interested into `Res::Def`. And in there, we only want "items" which get their own // rustdoc page. So not `DefKind::Ctor` for example (which is returned by `tcx.module_children()`). fn should_ignore_res(res: Res) -> bool { + !matches!(res, Res::Def(def_kind, _) if !should_ignore_def_kind(def_kind)) +} + +fn should_ignore_def_kind(kind: DefKind) -> bool { !matches!( - res, - Res::Def(DefKind::Trait, _) - | Res::Def(DefKind::TraitAlias, _) - | Res::Def(DefKind::Fn, _) - | Res::Def(DefKind::Struct, _) - | Res::Def(DefKind::Union, _) - | Res::Def(DefKind::TyAlias, _) - | Res::Def(DefKind::Enum, _) - | Res::Def(DefKind::ForeignTy, _) - | Res::Def(DefKind::Variant, _) - | Res::Def(DefKind::Mod, _) - | Res::Def(DefKind::Static { .. }, _) - | Res::Def(DefKind::Const { .. }, _) - | Res::Def(DefKind::Macro(_), _) + kind, + DefKind::Trait + | DefKind::TraitAlias + | DefKind::Fn + | DefKind::Struct + | DefKind::Union + | DefKind::TyAlias + | DefKind::Enum + | DefKind::ForeignTy + | DefKind::Variant + | DefKind::Mod + | DefKind::Static { .. } + | DefKind::Const { .. } + | DefKind::Macro(_) + | DefKind::Use ) } @@ -770,13 +775,15 @@ fn build_module_items( } else if let Some(def_id) = res.opt_def_id() && let Some(reexport) = item.reexport_chain.first() && let Some(reexport_def_id) = reexport.id() + && !should_ignore_def_kind(cx.tcx.def_kind(reexport_def_id)) && find_attr!( load_attrs(cx.tcx, reexport_def_id), Doc(d) if d.inline.first().is_some_and(|(inline, _)| *inline == hir::attrs::DocInline::NoInline) ) { - if should_ignore_res(res) { + // We don't inline foreign `use`. + if should_ignore_res(res) || matches!(res, Res::Def(DefKind::Use, _)) { continue; } // This item is reexported as `no_inline` so it shouldn't be inlined. diff --git a/tests/rustdoc-html/reexport/auxiliary/glob-import.rs b/tests/rustdoc-html/reexport/auxiliary/glob-import.rs new file mode 100644 index 0000000000000..2f8114586c592 --- /dev/null +++ b/tests/rustdoc-html/reexport/auxiliary/glob-import.rs @@ -0,0 +1 @@ +pub extern crate std as wgc; diff --git a/tests/rustdoc-html/reexport/glob-import.rs b/tests/rustdoc-html/reexport/glob-import.rs new file mode 100644 index 0000000000000..1bdfc7bdce102 --- /dev/null +++ b/tests/rustdoc-html/reexport/glob-import.rs @@ -0,0 +1,17 @@ +// This test ensures that inlining a foreign crate through a glob import doesn't +// panic (because we're trying to retrieve attributes from an item which doesn't +// have attributes). +// Regression test for . + +//@ aux-build: glob-import.rs + +#![crate_name = "foo"] + +extern crate glob_import; + +//@ has 'foo/index.html' +// There should be only one item listed: `wgc` (the only `glob-import` dep public item). +//@ count - '//*[@id="main-content"]/h2[@class="section-header"]' 1 +//@ count - '//*[@id="main-content"]/dl[@class="item-table"]/dt' 1 + +pub use glob_import::*;