From 0ef4e1154335de75d5c333bf61e192ec99758fb3 Mon Sep 17 00:00:00 2001 From: ajasad25 Date: Fri, 22 May 2026 15:32:48 +0500 Subject: [PATCH 01/14] style: Clarify nullary call and `()` no-break rule applies past max width --- src/doc/style-guide/src/expressions.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/doc/style-guide/src/expressions.md b/src/doc/style-guide/src/expressions.md index 9df5d7d18edb5..53fd7df0cc972 100644 --- a/src/doc/style-guide/src/expressions.md +++ b/src/doc/style-guide/src/expressions.md @@ -185,7 +185,9 @@ let f = Foo { ## Unit literals -Never break between the opening and closing parentheses of the `()` unit literal. +Never break between the opening and closing parentheses of the `()` unit +literal. This applies even when the closing parenthesis would fall past the +maximum line width. ## Tuple literals @@ -384,7 +386,8 @@ Prefer not to break a line in the callee expression. For a function call with no arguments (a nullary function call like `func()`), never break within the parentheses, and never put a space between the parentheses. Always write a nullary function call as a single-line call, never -a multi-line call. +a multi-line call. This applies even when the closing parenthesis would fall +past the maximum line width. ### Single-line calls From 33f59590d381606c77409820f3321df6ca976605 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 29 Apr 2026 13:29:09 +1000 Subject: [PATCH 02/14] Remove unused functions in `value_analysis.rs` And reduce visibility of functions only used within the crate. --- .../rustc_mir_dataflow/src/value_analysis.rs | 62 ++----------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs index 2476dcd5bf87a..1e04453680e81 100644 --- a/compiler/rustc_mir_dataflow/src/value_analysis.rs +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -120,17 +120,6 @@ impl State { State::Reachable(StateData::new()) } - pub fn all_bottom(&self) -> bool { - match self { - State::Unreachable => false, - State::Reachable(values) => - { - #[allow(rustc::potential_query_instability)] - values.map.values().all(V::is_bottom) - } - } - } - pub fn is_reachable(&self) -> bool { matches!(self, State::Reachable(_)) } @@ -168,7 +157,7 @@ impl State { /// /// `tail_elem` allows to support discriminants that are not a place in MIR, but that we track /// as such. - pub fn flood_with_tail_elem( + fn flood_with_tail_elem( &mut self, place: PlaceRef<'_>, tail_elem: Option, @@ -238,25 +227,19 @@ impl State { } /// Retrieve the value stored for a place, or `None` if it is not tracked. - pub fn try_get(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { + fn try_get(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { let place = map.find(place)?; self.try_get_idx(place, map) } /// Retrieve the discriminant stored for a place, or `None` if it is not tracked. - pub fn try_get_discr(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { + fn try_get_discr(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { let place = map.find_discr(place)?; self.try_get_idx(place, map) } - /// Retrieve the slice length stored for a place, or `None` if it is not tracked. - pub fn try_get_len(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option { - let place = map.find_len(place)?; - self.try_get_idx(place, map) - } - /// Retrieve the value stored for a place index, or `None` if it is not tracked. - pub fn try_get_idx(&self, place: PlaceIndex, map: &Map<'_>) -> Option { + fn try_get_idx(&self, place: PlaceIndex, map: &Map<'_>) -> Option { match self { State::Reachable(values) => { map.places[place].value_index.map(|v| values.get(v).clone()) @@ -293,20 +276,6 @@ impl State { } } - /// Retrieve the value stored for a place, or ⊤ if it is not tracked. - /// - /// This method returns ⊥ the current state is unreachable. - pub fn get_len(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V - where - V: HasBottom + HasTop, - { - match self { - State::Reachable(_) => self.try_get_len(place, map).unwrap_or(V::TOP), - // Because this is unreachable, we can return any value we want. - State::Unreachable => V::BOTTOM, - } - } - /// Retrieve the value stored for a place index, or ⊤ if it is not tracked. /// /// This method returns ⊥ the current state is unreachable. @@ -800,21 +769,6 @@ impl<'tcx> Map<'tcx> { self.places[place].value_index } - /// Locates the value corresponding to the given place. - pub fn find_value(&self, place: PlaceRef<'_>) -> Option { - self.value(self.find(place)?) - } - - /// Locates the value corresponding to the given discriminant. - pub fn find_discr_value(&self, place: PlaceRef<'_>) -> Option { - self.value(self.find_discr(place)?) - } - - /// Locates the value corresponding to the given length. - pub fn find_len_value(&self, place: PlaceRef<'_>) -> Option { - self.value(self.find_len(place)?) - } - /// Iterate over all direct children. fn children(&self, parent: PlaceIndex) -> impl Iterator { Children::new(self, parent) @@ -882,12 +836,6 @@ impl<'tcx> Map<'tcx> { } } - /// Return the range of value indices inside this place. - pub fn values_inside(&self, root: PlaceIndex) -> &[ValueIndex] { - let range = self.inner_values[root].clone(); - &self.inner_values_buffer[range] - } - /// Invoke a function on each value in the given place and all descendants. #[tracing::instrument(level = "trace", skip(self, f))] fn for_each_value_inside(&self, root: PlaceIndex, f: &mut impl FnMut(ValueIndex)) { @@ -937,7 +885,7 @@ impl<'tcx> Map<'tcx> { /// Recursively iterates on each value contained in `target`, paired with matching projection /// inside `source`. - pub fn for_each_value_pair( + fn for_each_value_pair( &self, target: PlaceIndex, source: PlaceIndex, From bdf8d768d5251e60f61e2b8e2f3a23212fbfe131 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 1 May 2026 15:32:22 +1000 Subject: [PATCH 03/14] Reduce more visibilities --- compiler/rustc_mir_dataflow/src/framework/fmt.rs | 14 +++++++------- compiler/rustc_mir_dataflow/src/move_paths/mod.rs | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/framework/fmt.rs b/compiler/rustc_mir_dataflow/src/framework/fmt.rs index 38599cd094933..5578b435cb33f 100644 --- a/compiler/rustc_mir_dataflow/src/framework/fmt.rs +++ b/compiler/rustc_mir_dataflow/src/framework/fmt.rs @@ -41,9 +41,9 @@ pub trait DebugWithContext: Eq + fmt::Debug { } /// Implements `fmt::Debug` by deferring to `>::fmt_with`. -pub struct DebugWithAdapter<'a, T, C> { - pub this: T, - pub ctxt: &'a C, +pub(crate) struct DebugWithAdapter<'a, T, C> { + pub(crate) this: T, + pub(crate) ctxt: &'a C, } impl fmt::Debug for DebugWithAdapter<'_, T, C> @@ -56,10 +56,10 @@ where } /// Implements `fmt::Debug` by deferring to `>::fmt_diff_with`. -pub struct DebugDiffWithAdapter<'a, T, C> { - pub new: T, - pub old: T, - pub ctxt: &'a C, +pub(crate) struct DebugDiffWithAdapter<'a, T, C> { + pub(crate) new: T, + pub(crate) old: T, + pub(crate) ctxt: &'a C, } impl fmt::Debug for DebugDiffWithAdapter<'_, T, C> diff --git a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs index 8be7d210f8dfd..7f8872b3e3493 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs @@ -388,7 +388,7 @@ impl<'tcx> MoveData<'tcx> { /// A projection into a move path producing a child path #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum MoveSubPath { +enum MoveSubPath { Deref, Field(FieldIdx), ConstantIndex(u64), @@ -397,7 +397,7 @@ pub enum MoveSubPath { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MoveSubPathResult { +enum MoveSubPathResult { One(MoveSubPath), Subslice { from: u64, to: u64 }, Skip, @@ -405,7 +405,7 @@ pub enum MoveSubPathResult { } impl MoveSubPath { - pub fn of(elem: ProjectionKind) -> MoveSubPathResult { + fn of(elem: ProjectionKind) -> MoveSubPathResult { let subpath = match elem { // correspond to a MoveSubPath ProjectionKind::Deref => MoveSubPath::Deref, From d2bb77279d7bb9cde825584ec143124ced907b0b Mon Sep 17 00:00:00 2001 From: Andrii Anoshyn Date: Wed, 27 May 2026 22:13:43 +0300 Subject: [PATCH 04/14] Note irrefutable while let in loop type errors --- compiler/rustc_hir_typeck/src/coercion.rs | 28 ++++++++- tests/ui/coercion/coerce-loop-issue-122561.rs | 18 ++++++ .../coercion/coerce-loop-issue-122561.stderr | 62 +++++++++++++++++-- 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 7a25b5e5e7a4e..d8881de576eb4 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1881,7 +1881,7 @@ impl<'tcx> CoerceMany<'tcx> { if let Some(expr) = expression { if let hir::ExprKind::Loop( - _, + block, _, loop_src @ (hir::LoopSource::While | hir::LoopSource::ForLoop), _, @@ -1894,6 +1894,14 @@ impl<'tcx> CoerceMany<'tcx> { }; err.note(format!("{loop_type} evaluate to unit type `()`")); + if loop_src == hir::LoopSource::While + && let Some(pat) = irrefutable_while_let_pattern(block) + { + err.span_note( + pat.span, + "this pattern always matches, so the loop condition never fails", + ); + } } fcx.emit_coerce_suggestions( @@ -2126,6 +2134,24 @@ impl<'tcx> CoerceMany<'tcx> { } } +fn irrefutable_while_let_pattern<'hir>(block: &hir::Block<'hir>) -> Option<&'hir hir::Pat<'hir>> { + let hir::ExprKind::If(cond, _, _) = block.expr?.kind else { + return None; + }; + let hir::ExprKind::Let(let_expr) = cond.kind else { + return None; + }; + simple_irrefutable_pattern(let_expr.pat).then_some(let_expr.pat) +} + +fn simple_irrefutable_pattern(pat: &hir::Pat<'_>) -> bool { + match pat.kind { + hir::PatKind::Wild | hir::PatKind::Binding(_, _, _, None) => true, + hir::PatKind::Tuple(pats, _) => pats.iter().all(simple_irrefutable_pattern), + _ => false, + } +} + /// Recursively visit goals to decide whether an unsizing is possible. /// `Break`s when it isn't, and an error should be raised. /// `Continue`s when an unsizing ok based on an implementation of the `Unsize` trait / lang item. diff --git a/tests/ui/coercion/coerce-loop-issue-122561.rs b/tests/ui/coercion/coerce-loop-issue-122561.rs index d79dfa28b0daf..f116f5fcddda5 100644 --- a/tests/ui/coercion/coerce-loop-issue-122561.rs +++ b/tests/ui/coercion/coerce-loop-issue-122561.rs @@ -69,6 +69,24 @@ fn while_zero_times() -> bool { } } +fn while_let_binding() -> bool { + while let x = false { + //~^ ERROR mismatched types + if x { + return true; + } + } +} + +fn while_let_tuple() -> bool { + while let (x, _) = (false, true) { + //~^ ERROR mismatched types + if x { + return true; + } + } +} + fn while_never_type() -> ! { while true { //~^ ERROR mismatched types diff --git a/tests/ui/coercion/coerce-loop-issue-122561.stderr b/tests/ui/coercion/coerce-loop-issue-122561.stderr index 3fd6671565f18..ef7a3049465ad 100644 --- a/tests/ui/coercion/coerce-loop-issue-122561.stderr +++ b/tests/ui/coercion/coerce-loop-issue-122561.stderr @@ -7,7 +7,7 @@ LL | while true { = note: `#[warn(while_true)]` on by default warning: denote infinite loops with `loop { ... }` - --> $DIR/coerce-loop-issue-122561.rs:73:5 + --> $DIR/coerce-loop-issue-122561.rs:91:5 | LL | while true { | ^^^^^^^^^^ help: use `loop` @@ -171,6 +171,56 @@ LL + /* `bool` value */ error[E0308]: mismatched types --> $DIR/coerce-loop-issue-122561.rs:73:5 | +LL | fn while_let_binding() -> bool { + | ---- expected `bool` because of return type +LL | / while let x = false { +LL | | +LL | | if x { +LL | | return true; +LL | | } +LL | | } + | |_____^ expected `bool`, found `()` + | + = note: `while` loops evaluate to unit type `()` +note: this pattern always matches, so the loop condition never fails + --> $DIR/coerce-loop-issue-122561.rs:73:15 + | +LL | while let x = false { + | ^ +help: consider returning a value here + | +LL ~ } +LL + /* `bool` value */ + | + +error[E0308]: mismatched types + --> $DIR/coerce-loop-issue-122561.rs:82:5 + | +LL | fn while_let_tuple() -> bool { + | ---- expected `bool` because of return type +LL | / while let (x, _) = (false, true) { +LL | | +LL | | if x { +LL | | return true; +LL | | } +LL | | } + | |_____^ expected `bool`, found `()` + | + = note: `while` loops evaluate to unit type `()` +note: this pattern always matches, so the loop condition never fails + --> $DIR/coerce-loop-issue-122561.rs:82:15 + | +LL | while let (x, _) = (false, true) { + | ^^^^^^ +help: consider returning a value here + | +LL ~ } +LL + /* `bool` value */ + | + +error[E0308]: mismatched types + --> $DIR/coerce-loop-issue-122561.rs:91:5 + | LL | fn while_never_type() -> ! { | - expected `!` because of return type LL | / while true { @@ -188,7 +238,7 @@ LL + /* `loop {}` or `panic!("...")` */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:87:5 + --> $DIR/coerce-loop-issue-122561.rs:105:5 | LL | / for i in 0.. { LL | | @@ -203,7 +253,7 @@ LL + /* `i32` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:94:9 + --> $DIR/coerce-loop-issue-122561.rs:112:9 | LL | / for i in 0..5 { LL | | @@ -218,7 +268,7 @@ LL + /* `usize` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:100:9 + --> $DIR/coerce-loop-issue-122561.rs:118:9 | LL | / while false { LL | | @@ -233,7 +283,7 @@ LL + /* `usize` value */ | error[E0308]: mismatched types - --> $DIR/coerce-loop-issue-122561.rs:106:23 + --> $DIR/coerce-loop-issue-122561.rs:124:23 | LL | let _ = |a: &[(); for x in 0..2 {}]| {}; | ^^^^^^^^^^^^^^^^ expected `usize`, found `()` @@ -244,6 +294,6 @@ help: consider returning a value here LL | let _ = |a: &[(); for x in 0..2 {} /* `usize` value */]| {}; | +++++++++++++++++++ -error: aborting due to 14 previous errors; 2 warnings emitted +error: aborting due to 16 previous errors; 2 warnings emitted For more information about this error, try `rustc --explain E0308`. From cfbe6af4f25d12f7eade4106f978363d11c4d125 Mon Sep 17 00:00:00 2001 From: cijiugechu Date: Thu, 28 May 2026 10:15:29 +0800 Subject: [PATCH 05/14] Fix tupled closure signature in AsyncFn arg mismatch diagnostic --- .../rustc_middle/src/middle/lang_items.rs | 19 +++++++++++++++ .../src/error_reporting/traits/suggestions.rs | 2 +- .../async-fn/closure-arg-type-mismatch.rs | 9 +++++++ .../async-fn/closure-arg-type-mismatch.stderr | 24 +++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/ui/async-await/async-fn/closure-arg-type-mismatch.rs create mode 100644 tests/ui/async-await/async-fn/closure-arg-type-mismatch.stderr diff --git a/compiler/rustc_middle/src/middle/lang_items.rs b/compiler/rustc_middle/src/middle/lang_items.rs index a0e4c288c4a85..8a2595e2938b8 100644 --- a/compiler/rustc_middle/src/middle/lang_items.rs +++ b/compiler/rustc_middle/src/middle/lang_items.rs @@ -55,6 +55,25 @@ impl<'tcx> TyCtxt<'tcx> { } } + /// Given a [`DefId`], returns whether it is one of the built-in callable + /// traits: `Fn`/`FnMut`/`FnOnce` or `AsyncFn`/`AsyncFnMut`/`AsyncFnOnce`. + /// + /// These built-in callable traits all model their inputs using the + /// `rust-call` ABI, which is tupled at the type level. + pub fn is_callable_trait(self, id: DefId) -> bool { + matches!( + self.as_lang_item(id), + Some( + LangItem::Fn + | LangItem::FnMut + | LangItem::FnOnce + | LangItem::AsyncFn + | LangItem::AsyncFnMut + | LangItem::AsyncFnOnce + ) + ) + } + /// Given a [`ty::ClosureKind`], get the [`DefId`] of its corresponding `Fn`-family /// trait, if it is defined. pub fn fn_trait_kind_to_def_id(self, kind: ty::ClosureKind) -> Option { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 66eaa49cbd5d4..beea76434f1b3 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -2346,7 +2346,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ) -> Ty<'tcx> { let inputs = trait_ref.args.type_at(1); let sig = match inputs.kind() { - ty::Tuple(inputs) if infcx.tcx.is_fn_trait(trait_ref.def_id) => { + ty::Tuple(inputs) if infcx.tcx.is_callable_trait(trait_ref.def_id) => { infcx.tcx.mk_fn_sig_safe_rust_abi(*inputs, infcx.next_ty_var(DUMMY_SP)) } _ => infcx.tcx.mk_fn_sig_safe_rust_abi([inputs], infcx.next_ty_var(DUMMY_SP)), diff --git a/tests/ui/async-await/async-fn/closure-arg-type-mismatch.rs b/tests/ui/async-await/async-fn/closure-arg-type-mismatch.rs new file mode 100644 index 0000000000000..be1ad20e4c221 --- /dev/null +++ b/tests/ui/async-await/async-fn/closure-arg-type-mismatch.rs @@ -0,0 +1,9 @@ +//! Regression test for +//@ edition:2021 + +fn foo(_: impl AsyncFn(&mut i32)) {} + +fn main() { + foo(|_: i32| async {}); + //~^ ERROR type mismatch in closure arguments +} diff --git a/tests/ui/async-await/async-fn/closure-arg-type-mismatch.stderr b/tests/ui/async-await/async-fn/closure-arg-type-mismatch.stderr new file mode 100644 index 0000000000000..a3b47bb448d17 --- /dev/null +++ b/tests/ui/async-await/async-fn/closure-arg-type-mismatch.stderr @@ -0,0 +1,24 @@ +error[E0631]: type mismatch in closure arguments + --> $DIR/closure-arg-type-mismatch.rs:7:5 + | +LL | foo(|_: i32| async {}); + | ^^^^--------^^^^^^^^^^ + | | | + | | found signature defined here + | expected due to this + | + = note: expected closure signature `for<'a> fn(&'a mut _) -> _` + found closure signature `fn(_) -> _` +note: required by a bound in `foo` + --> $DIR/closure-arg-type-mismatch.rs:4:16 + | +LL | fn foo(_: impl AsyncFn(&mut i32)) {} + | ^^^^^^^^^^^^^^^^^ required by this bound in `foo` +help: consider adjusting the signature so it borrows its argument + | +LL | foo(|_: &mut i32| async {}); + | ++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0631`. From e731b4e5f9da236a0525e6a815c4261aa835b334 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 27 May 2026 15:09:42 +0000 Subject: [PATCH 06/14] Allow two object files for a single CGU in CompiledModule cg_clif needs this as it passes inline assembly to an external assembler, producing a separate object file. Currently it emits multiple CompiledModules, but this is not compatible with using the codegen coordinator of cg_ssa. --- .../rustc_codegen_cranelift/src/driver/aot.rs | 66 +++++++------------ .../rustc_codegen_cranelift/src/global_asm.rs | 18 +---- compiler/rustc_codegen_ssa/src/back/link.rs | 57 ++++++++++++---- compiler/rustc_codegen_ssa/src/back/write.rs | 15 +++++ compiler/rustc_codegen_ssa/src/lib.rs | 2 + 5 files changed, 85 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index 3781ad7b3b83f..323fec06bcc58 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -35,8 +35,7 @@ fn disable_incr_cache() -> bool { } struct ModuleCodegenResult { - module_regular: CompiledModule, - module_global_asm: Option, + module: CompiledModule, existing_work_product: Option<(WorkProductId, WorkProduct)>, } @@ -80,29 +79,25 @@ impl OngoingCodegen { Ok(module_codegen_result) => module_codegen_result, Err(err) => sess.dcx().fatal(err), }; - let ModuleCodegenResult { module_regular, module_global_asm, existing_work_product } = - module_codegen_result; + let ModuleCodegenResult { module, existing_work_product } = module_codegen_result; if let Some((work_product_id, work_product)) = existing_work_product { work_products.insert(work_product_id, work_product); } else { let work_product = if disable_incr_cache { None - } else if let Some(module_global_asm) = &module_global_asm { + } else if let Some(global_asm_object) = &module.global_asm_object { rustc_incremental::copy_cgu_workproduct_to_incr_comp_cache_dir( sess, - &module_regular.name, - &[ - ("o", module_regular.object.as_ref().unwrap()), - ("asm.o", module_global_asm.object.as_ref().unwrap()), - ], + &module.name, + &[("o", module.object.as_ref().unwrap()), ("asm.o", global_asm_object)], &[], ) } else { rustc_incremental::copy_cgu_workproduct_to_incr_comp_cache_dir( sess, - &module_regular.name, - &[("o", module_regular.object.as_ref().unwrap())], + &module.name, + &[("o", module.object.as_ref().unwrap())], &[], ) }; @@ -111,10 +106,7 @@ impl OngoingCodegen { } } - modules.push(module_regular); - if let Some(module_global_asm) = module_global_asm { - modules.push(module_global_asm); - } + modules.push(module); } self.concurrency_limiter.finished(); @@ -163,29 +155,17 @@ fn emit_cgu( debug.emit(&mut product); } - let module_regular = emit_module( + let module = emit_module( output_filenames, prof, product.object, ModuleKind::Regular, name.clone(), + global_asm_object_file, producer, )?; - Ok(ModuleCodegenResult { - module_regular, - module_global_asm: global_asm_object_file.map(|global_asm_object_file| CompiledModule { - name: format!("{name}.asm"), - kind: ModuleKind::Regular, - object: Some(global_asm_object_file), - dwarf_object: None, - bytecode: None, - assembly: None, - llvm_ir: None, - links_from_incr_cache: Vec::new(), - }), - existing_work_product: None, - }) + Ok(ModuleCodegenResult { module, existing_work_product: None }) } fn emit_module( @@ -194,6 +174,7 @@ fn emit_module( mut object: cranelift_object::object::write::Object<'_>, kind: ModuleKind, name: String, + global_asm_object: Option, producer_str: &str, ) -> Result { if object.format() == cranelift_object::object::BinaryFormat::Elf { @@ -235,6 +216,7 @@ fn emit_module( name, kind, object: Some(tmp_file), + global_asm_object, dwarf_object: None, bytecode: None, assembly: None, @@ -265,7 +247,7 @@ fn reuse_workproduct_for_cgu( } let obj_out_global_asm = - crate::global_asm::add_file_stem_postfix(obj_out_regular.clone(), ".asm"); + tcx.output_filenames(()).temp_path_ext_for_cgu("asm.o", cgu.name().as_str()); let source_file_global_asm = if let Some(asm_o) = work_product.saved_files.get("asm.o") { let source_file_global_asm = rustc_incremental::in_incr_comp_dir_sess(tcx.sess, asm_o); if let Err(err) = rustc_fs_util::link_or_copy(&source_file_global_asm, &obj_out_global_asm) @@ -283,26 +265,21 @@ fn reuse_workproduct_for_cgu( }; Ok(ModuleCodegenResult { - module_regular: CompiledModule { + module: CompiledModule { name: cgu.name().to_string(), kind: ModuleKind::Regular, object: Some(obj_out_regular), + global_asm_object: source_file_global_asm.as_ref().map(|_| obj_out_global_asm), dwarf_object: None, bytecode: None, assembly: None, llvm_ir: None, - links_from_incr_cache: vec![source_file_regular], + links_from_incr_cache: if let Some(source_file_global_asm) = source_file_global_asm { + vec![source_file_regular, source_file_global_asm] + } else { + vec![source_file_regular] + }, }, - module_global_asm: source_file_global_asm.map(|source_file| CompiledModule { - name: cgu.name().to_string(), - kind: ModuleKind::Regular, - object: Some(obj_out_global_asm), - dwarf_object: None, - bytecode: None, - assembly: None, - llvm_ir: None, - links_from_incr_cache: vec![source_file], - }), existing_work_product: Some((cgu.work_product_id(), work_product)), }) } @@ -447,6 +424,7 @@ fn emit_allocator_module(tcx: TyCtxt<'_>) -> Option { product.object, ModuleKind::Allocator, "allocator_shim".to_owned(), + None, &crate::debuginfo::producer(tcx.sess), ) { Ok(allocator_module) => Some(allocator_module), diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs index 0c5f4136a32d6..ee7e6732e6a77 100644 --- a/compiler/rustc_codegen_cranelift/src/global_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs @@ -12,7 +12,7 @@ use rustc_middle::ty::TyCtxt; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTyCtxt, HasTypingEnv, LayoutError, LayoutOfHelpers, }; -use rustc_session::config::{OutputFilenames, OutputType}; +use rustc_session::config::OutputFilenames; use rustc_target::asm::InlineAsmArch; use crate::prelude::*; @@ -198,10 +198,7 @@ pub(crate) fn compile_global_asm( .join("\n"); global_asm.push('\n'); - let global_asm_object_file = add_file_stem_postfix( - config.output_filenames.temp_path_for_cgu(OutputType::Object, cgu_name), - ".asm", - ); + let global_asm_object_file = config.output_filenames.temp_path_ext_for_cgu("asm.o", cgu_name); // Assemble `global_asm` if option_env!("CG_CLIF_FORCE_GNU_AS").is_some() { @@ -271,14 +268,3 @@ pub(crate) fn compile_global_asm( Ok(Some(global_asm_object_file)) } - -pub(crate) fn add_file_stem_postfix(mut path: PathBuf, postfix: &str) -> PathBuf { - let mut new_filename = path.file_stem().unwrap().to_owned(); - new_filename.push(postfix); - if let Some(extension) = path.extension() { - new_filename.push("."); - new_filename.push(extension); - } - path.set_file_name(new_filename); - path -} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index d2c82280ad2ab..0152030ea875b 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -98,8 +98,13 @@ pub fn link_binary( } sess.time("link_binary_check_files_are_writeable", || { - for obj in compiled_modules.modules.iter().filter_map(|m| m.object.as_ref()) { - check_file_is_writeable(obj, sess); + for m in &compiled_modules.modules { + if let Some(obj) = &m.object { + check_file_is_writeable(obj, sess); + } + if let Some(obj) = &m.global_asm_object { + check_file_is_writeable(obj, sess); + } } }); @@ -202,6 +207,10 @@ pub fn link_binary( ensure_removed(sess.dcx(), obj); } + if !preserve_objects && let Some(ref obj) = module.global_asm_object { + ensure_removed(sess.dcx(), obj); + } + if !preserve_dwarf_objects && let Some(ref dwo_obj) = module.dwarf_object { ensure_removed(sess.dcx(), dwo_obj); } @@ -307,6 +316,7 @@ fn link_rlib<'a>( .modules .iter() .filter_map(|m| m.object.as_ref()) + .chain(compiled_modules.modules.iter().filter_map(|m| m.global_asm_object.as_ref())) .map(|obj| obj.file_name().unwrap().to_str().unwrap().to_string()) .collect(); @@ -352,6 +362,10 @@ fn link_rlib<'a>( ab.add_file(obj); } + if let Some(obj) = m.global_asm_object.as_ref() { + ab.add_file(obj); + } + if let Some(dwarf_obj) = m.dwarf_object.as_ref() { ab.add_file(dwarf_obj); } @@ -360,9 +374,13 @@ fn link_rlib<'a>( match flavor { RlibFlavor::Normal => {} RlibFlavor::StaticlibBase => { - let obj = compiled_modules.allocator_module.as_ref().and_then(|m| m.object.as_ref()); - if let Some(obj) = obj { - ab.add_file(obj); + if let Some(m) = &compiled_modules.allocator_module { + if let Some(obj) = &m.object { + ab.add_file(obj); + } + if let Some(obj) = &m.global_asm_object { + ab.add_file(obj); + } } } } @@ -632,8 +650,13 @@ fn link_dwarf_object( // Input objs contain .o/.dwo files from the current crate. match sess.opts.unstable_opts.split_dwarf_kind { SplitDwarfKind::Single => { - for input_obj in compiled_modules.modules.iter().filter_map(|m| m.object.as_ref()) { - package.add_input_object(input_obj)?; + for m in &compiled_modules.modules { + if let Some(input_obj) = &m.object { + package.add_input_object(input_obj)?; + } + if let Some(input_obj) = &m.global_asm_object { + package.add_input_object(input_obj)?; + } } } SplitDwarfKind::Split => { @@ -2243,8 +2266,13 @@ fn add_linked_symbol_object( /// Add object files containing code from the current crate. fn add_local_crate_regular_objects(cmd: &mut dyn Linker, compiled_modules: &CompiledModules) { - for obj in compiled_modules.modules.iter().filter_map(|m| m.object.as_ref()) { - cmd.add_object(obj); + for m in &compiled_modules.modules { + if let Some(obj) = &m.object { + cmd.add_object(obj); + } + if let Some(obj) = &m.global_asm_object { + cmd.add_object(obj); + } } } @@ -2255,10 +2283,13 @@ fn add_local_crate_allocator_objects( crate_info: &CrateInfo, crate_type: CrateType, ) { - if needs_allocator_shim_for_linking(&crate_info.dependency_formats, crate_type) { - if let Some(obj) = - compiled_modules.allocator_module.as_ref().and_then(|m| m.object.as_ref()) - { + if needs_allocator_shim_for_linking(&crate_info.dependency_formats, crate_type) + && let Some(m) = &compiled_modules.allocator_module + { + if let Some(obj) = &m.object { + cmd.add_object(obj); + } + if let Some(obj) = &m.global_asm_object { cmd.add_object(obj); } } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 7b22ac231df1c..54693d0a69527 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -474,6 +474,9 @@ fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( if let Some(object_file_path) = &module.object { files.push((OutputType::Object.extension(), object_file_path.as_path())); } + if let Some(global_asm_object_file_path) = &module.global_asm_object { + files.push(("asm.o", global_asm_object_file_path.as_path())); + } if let Some(dwarf_object_file_path) = &module.dwarf_object { files.push(("dwo", dwarf_object_file_path.as_path())); } @@ -620,6 +623,10 @@ pub fn produce_final_output_artifacts( ensure_removed(sess.dcx(), path); } + if let Some(ref path) = module.global_asm_object { + ensure_removed(sess.dcx(), path); + } + if let Some(ref path) = module.dwarf_object { ensure_removed(sess.dcx(), path); } @@ -924,6 +931,13 @@ fn execute_copy_from_cache_work_item( let llvm_ir = load_from_incr_cache(module_config.emit_ir, OutputType::LlvmAssembly); let bytecode = load_from_incr_cache(module_config.emit_bc, OutputType::Bitcode); let object = load_from_incr_cache(should_emit_obj, OutputType::Object); + let global_asm_object = + if should_emit_obj && let Some(saved_file) = module.source.saved_files.get("asm.o") { + let output_path = cgcx.output_filenames.temp_path_ext_for_cgu("asm.o", &module.name); + load_from_incr_comp_dir(output_path, &saved_file) + } else { + None + }; if should_emit_obj && object.is_none() { dcx.emit_fatal(errors::NoSavedObjectFile { cgu_name: &module.name }) } @@ -933,6 +947,7 @@ fn execute_copy_from_cache_work_item( kind: ModuleKind::Regular, name: module.name, object, + global_asm_object, dwarf_object, bytecode, assembly, diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index c7446bd784b40..560cbe98f7497 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -109,6 +109,7 @@ impl ModuleCodegen { name: self.name, kind: self.kind, object, + global_asm_object: None, dwarf_object, bytecode, assembly, @@ -123,6 +124,7 @@ pub struct CompiledModule { pub name: String, pub kind: ModuleKind, pub object: Option, + pub global_asm_object: Option, pub dwarf_object: Option, pub bytecode: Option, pub assembly: Option, // --emit=asm From 686c8c924cc385f5addfdfbadbeadeb8d568ef6f Mon Sep 17 00:00:00 2001 From: Ilia Novoselov Date: Fri, 29 May 2026 15:25:53 +0200 Subject: [PATCH 07/14] Expanded tests for &x -> &mut x suggestions --- .../borrowck-deref-pattern-assignment.rs | 47 ++++++++++ .../borrowck-deref-pattern-assignment.stderr | 87 +++++++++++++++++++ ...rowck-for-loop-deref-pattern-assignment.rs | 10 --- ...k-for-loop-deref-pattern-assignment.stderr | 17 ---- 4 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 tests/ui/borrowck/borrowck-deref-pattern-assignment.rs create mode 100644 tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr delete mode 100644 tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs delete mode 100644 tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr diff --git a/tests/ui/borrowck/borrowck-deref-pattern-assignment.rs b/tests/ui/borrowck/borrowck-deref-pattern-assignment.rs new file mode 100644 index 0000000000000..9d20f8a36aae4 --- /dev/null +++ b/tests/ui/borrowck/borrowck-deref-pattern-assignment.rs @@ -0,0 +1,47 @@ +//! regression test for +//! Ensure the diagnostic suggests `&(mut x)` (parenthesized) instead of `&mut x`. + +fn for_loop() { + let nums: &[u32] = &[1, 2, 3]; + for &num in nums { + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + println!("{num}"); + } +} + +fn let_deref(num_ref: &u32) -> u32 { + let &num = num_ref; + + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + + num +} + +fn deref_inside_pattern(option_num_ref: Option<&u32>) { + if let Some(&num) = option_num_ref { + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + + println!("{num}"); + } +} + +/// Insides of deref pattern do not need additional parens +fn inside_of_deref(num_option_ref: &Option) { + if let &Some(num) = num_option_ref { + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + + println!("{num}"); + } +} + +/// &mut deref pattern does not need additional parens +fn let_mut_deref(num_mut_ref: &mut u32) -> u32 { + let &mut num = num_mut_ref; + + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + + num +} + + +fn main() {} diff --git a/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr b/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr new file mode 100644 index 0000000000000..e0dd39c9ef8f4 --- /dev/null +++ b/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr @@ -0,0 +1,87 @@ +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck-deref-pattern-assignment.rs:7:9 + | +LL | for &num in nums { + | --- first assignment to `num` +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL - for &num in nums { +LL + for &(mut num) in nums { + | + +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck-deref-pattern-assignment.rs:15:5 + | +LL | let &num = num_ref; + | --- first assignment to `num` +LL | +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL | let &mut num = num_ref; + | +++ +help: to modify the original value, take a borrow instead + | +LL | let &ref mut num = num_ref; + | +++++++ + +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck-deref-pattern-assignment.rs:22:9 + | +LL | if let Some(&num) = option_num_ref { + | --- first assignment to `num` +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL | if let Some(&mut num) = option_num_ref { + | +++ +help: to modify the original value, take a borrow instead + | +LL | if let Some(&ref mut num) = option_num_ref { + | +++++++ + +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck-deref-pattern-assignment.rs:31:9 + | +LL | if let &Some(num) = num_option_ref { + | --- first assignment to `num` +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL | if let &Some(mut num) = num_option_ref { + | +++ +help: to modify the original value, take a borrow instead + | +LL | if let &Some(ref mut num) = num_option_ref { + | +++++++ + +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck-deref-pattern-assignment.rs:41:5 + | +LL | let &mut num = num_mut_ref; + | --- first assignment to `num` +LL | +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL | let &mut mut num = num_mut_ref; + | +++ +help: to modify the original value, take a borrow instead + | +LL | let &mut ref mut num = num_mut_ref; + | +++++++ + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0384`. diff --git a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs deleted file mode 100644 index fc4f1e4eacb95..0000000000000 --- a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! regression test for -//! Ensure the diagnostic suggests `for &(mut x) ...` (parenthesized) instead of `&mut x`. - -fn main() { - let nums: &[u32] = &[1, 2, 3]; - for &num in nums { - num *= 2; //~ ERROR cannot assign twice to immutable variable `num` - println!("{num}"); - } -} diff --git a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr deleted file mode 100644 index 3c4d0e966136d..0000000000000 --- a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0384]: cannot assign twice to immutable variable `num` - --> $DIR/borrowck-for-loop-deref-pattern-assignment.rs:7:9 - | -LL | for &num in nums { - | --- first assignment to `num` -LL | num *= 2; - | ^^^^^^^^ cannot assign twice to immutable variable - | -help: consider making this binding mutable - | -LL - for &num in nums { -LL + for &(mut num) in nums { - | - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0384`. From 9bef677785b802f9a7e6ba1e298a430f1b993477 Mon Sep 17 00:00:00 2001 From: Ilia Novoselov Date: Fri, 29 May 2026 15:32:44 +0200 Subject: [PATCH 08/14] Fixed &x -> &mut x suggestions for pattern matching --- .../src/diagnostics/conflict_errors.rs | 75 +++++++++---------- .../borrowck-deref-pattern-assignment.stderr | 10 ++- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 9a64a063fcf5c..993f04fe1c161 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -4023,7 +4023,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { && decl.can_be_made_mutable() { let mut is_for_loop = false; - let mut is_ref_pattern = false; + let mut is_immut_ref_pattern = false; if let LocalInfo::User(BindingForm::Var(VarBindingForm { opt_match_place: Some((_, match_span)), .. @@ -4031,55 +4031,52 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { { if matches!(match_span.desugaring_kind(), Some(DesugaringKind::ForLoop)) { is_for_loop = true; + } - if let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(self.mir_def_id()) { - struct RefPatternFinder<'tcx> { - tcx: TyCtxt<'tcx>, - binding_span: Span, - is_ref_pattern: bool, - } + if let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(self.mir_def_id()) { + struct RefPatternFinder<'tcx> { + tcx: TyCtxt<'tcx>, + binding_span: Span, + is_immut_ref_pattern: bool, + } - impl<'tcx> Visitor<'tcx> for RefPatternFinder<'tcx> { - type NestedFilter = OnlyBodies; + impl<'tcx> Visitor<'tcx> for RefPatternFinder<'tcx> { + type NestedFilter = OnlyBodies; - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.tcx - } + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.tcx + } - fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { - if !self.is_ref_pattern - && let hir::PatKind::Binding(_, _, ident, _) = pat.kind - && ident.span == self.binding_span - { - self.is_ref_pattern = - self.tcx.hir_parent_iter(pat.hir_id).any(|(_, node)| { - matches!( - node, - hir::Node::Pat(hir::Pat { - kind: hir::PatKind::Ref(..), - .. - }) - ) - }); - } - hir::intravisit::walk_pat(self, pat); + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + if !self.is_immut_ref_pattern + && let hir::PatKind::Binding(_, _, ident, _) = pat.kind + && ident.span == self.binding_span + && matches!( + self.tcx.parent_hir_node(pat.hir_id), + hir::Node::Pat(hir::Pat { + kind: hir::PatKind::Ref(_, _, hir::Mutability::Not), + .. + }) + ) + { + self.is_immut_ref_pattern = true; } + hir::intravisit::walk_pat(self, pat); } + } - let mut finder = RefPatternFinder { - tcx: self.infcx.tcx, - binding_span: decl.source_info.span, - is_ref_pattern: false, - }; + let mut finder = RefPatternFinder { + tcx: self.infcx.tcx, + binding_span: decl.source_info.span, + is_immut_ref_pattern: false, + }; - finder.visit_body(body); - is_ref_pattern = finder.is_ref_pattern; - } + finder.visit_body(body); + is_immut_ref_pattern = finder.is_immut_ref_pattern; } } - let (span, message) = if is_for_loop - && is_ref_pattern + let (span, message) = if is_immut_ref_pattern && let Ok(binding_name) = self.infcx.tcx.sess.source_map().span_to_snippet(decl.source_info.span) { diff --git a/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr b/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr index e0dd39c9ef8f4..65524e420d7fb 100644 --- a/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr +++ b/tests/ui/borrowck/borrowck-deref-pattern-assignment.stderr @@ -23,8 +23,9 @@ LL | num *= 2; | help: consider making this binding mutable | -LL | let &mut num = num_ref; - | +++ +LL - let &num = num_ref; +LL + let &(mut num) = num_ref; + | help: to modify the original value, take a borrow instead | LL | let &ref mut num = num_ref; @@ -40,8 +41,9 @@ LL | num *= 2; | help: consider making this binding mutable | -LL | if let Some(&mut num) = option_num_ref { - | +++ +LL - if let Some(&num) = option_num_ref { +LL + if let Some(&(mut num)) = option_num_ref { + | help: to modify the original value, take a borrow instead | LL | if let Some(&ref mut num) = option_num_ref { From 4f1630d3aadc3a4aa003134caaff25354ba1dc2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 29 May 2026 20:29:16 +0200 Subject: [PATCH 09/14] Move `compute_object_lifetime_bound` into submodule `dyn_trait` --- .../src/hir_ty_lowering/dyn_trait.rs | 42 +++++++++++++++++ .../src/hir_ty_lowering/mod.rs | 45 +------------------ 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index a498e97403881..818c418ad7c60 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -515,6 +515,48 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ); } + /// Given the bounds on an object, determines what single region bound (if any) we can + /// use to summarize this type. + /// + /// The basic idea is that we will use the bound the user + /// provided, if they provided one, and otherwise search the supertypes of trait bounds + /// for region bounds. It may be that we can derive no bound at all, in which case + /// we return `None`. + #[instrument(level = "debug", skip(self, span), ret)] + fn compute_object_lifetime_bound( + &self, + span: Span, + existential_predicates: &'tcx ty::List>, + ) -> Option> // if None, use the default + { + let tcx = self.tcx(); + + // No explicit region bound specified. Therefore, examine trait + // bounds and see if we can derive region bounds from those. + let derived_region_bounds = traits::wf::object_region_bounds(tcx, existential_predicates); + + // If there are no derived region bounds, then report back that we + // can find no region bound. The caller will use the default. + if derived_region_bounds.is_empty() { + return None; + } + + // If any of the derived region bounds are 'static, that is always + // the best choice. + if derived_region_bounds.iter().any(|r| r.is_static()) { + return Some(tcx.lifetimes.re_static); + } + + // Determine whether there is exactly one unique region in the set + // of derived region bounds. If so, use that. Otherwise, report an + // error. + let r = derived_region_bounds[0]; + if derived_region_bounds[1..].iter().any(|r1| r != *r1) { + self.dcx().emit_err(crate::errors::AmbiguousLifetimeBound { span }); + } + Some(r) + } + /// Prohibit or lint against *bare* trait object types depending on the edition. /// /// *Bare* trait object types are ones that aren't preceded by the keyword `dyn`. diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 5a86e8186a5aa..89bf398a9d166 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -47,12 +47,11 @@ use rustc_session::errors::feature_err; use rustc_session::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS; use rustc_span::{DUMMY_SP, Ident, Span, kw, sym}; use rustc_trait_selection::infer::InferCtxtExt; -use rustc_trait_selection::traits::wf::object_region_bounds; use rustc_trait_selection::traits::{self, FulfillmentError}; use tracing::{debug, instrument}; use crate::check::check_abi; -use crate::errors::{AmbiguousLifetimeBound, BadReturnTypeNotation, NoFieldOnType}; +use crate::errors::{BadReturnTypeNotation, NoFieldOnType}; use crate::hir_ty_lowering::errors::{GenericsArgsErrExtend, prohibit_assoc_item_constraint}; use crate::hir_ty_lowering::generics::{check_generic_arg_count, lower_generic_args}; use crate::middle::resolve_bound_vars as rbv; @@ -3738,48 +3737,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } - /// Given the bounds on an object, determines what single region bound (if any) we can - /// use to summarize this type. - /// - /// The basic idea is that we will use the bound the user - /// provided, if they provided one, and otherwise search the supertypes of trait bounds - /// for region bounds. It may be that we can derive no bound at all, in which case - /// we return `None`. - #[instrument(level = "debug", skip(self, span), ret)] - fn compute_object_lifetime_bound( - &self, - span: Span, - existential_predicates: &'tcx ty::List>, - ) -> Option> // if None, use the default - { - let tcx = self.tcx(); - - // No explicit region bound specified. Therefore, examine trait - // bounds and see if we can derive region bounds from those. - let derived_region_bounds = object_region_bounds(tcx, existential_predicates); - - // If there are no derived region bounds, then report back that we - // can find no region bound. The caller will use the default. - if derived_region_bounds.is_empty() { - return None; - } - - // If any of the derived region bounds are 'static, that is always - // the best choice. - if derived_region_bounds.iter().any(|r| r.is_static()) { - return Some(tcx.lifetimes.re_static); - } - - // Determine whether there is exactly one unique region in the set - // of derived region bounds. If so, use that. Otherwise, report an - // error. - let r = derived_region_bounds[0]; - if derived_region_bounds[1..].iter().any(|r1| r != *r1) { - self.dcx().emit_err(AmbiguousLifetimeBound { span }); - } - Some(r) - } - fn construct_const_ctor_value( &self, ctor_def_id: DefId, From a866e4279db74d724eba32c2369ca0fa8606f76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 29 May 2026 16:22:53 +0200 Subject: [PATCH 10/14] Rename `AmbiguousAssocItem` to `AmbiguityBetweenVariantAndAssocItem` and move its `Diagnostic` impl --- .../src/hir_ty_lowering/errors.rs | 52 +++++++++++++++++++ .../src/hir_ty_lowering/mod.rs | 52 +------------------ 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 5e11819805e15..710d527146a53 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -1847,6 +1847,58 @@ fn generics_args_err_extend<'a>( } } +pub(super) struct AmbiguityBetweenVariantAndAssocItem<'tcx> { + pub(super) variant_def_id: DefId, + pub(super) item_def_id: DefId, + pub(super) span: Span, + pub(super) segment_ident: Ident, + pub(super) bound_def_id: DefId, + pub(super) self_ty: Ty<'tcx>, + pub(super) tcx: TyCtxt<'tcx>, + pub(super) mode: super::LowerTypeRelativePathMode, +} + +impl<'a, 'tcx> rustc_errors::Diagnostic<'a, ()> for AmbiguityBetweenVariantAndAssocItem<'tcx> { + fn into_diag( + self, + dcx: rustc_errors::DiagCtxtHandle<'a>, + level: rustc_errors::Level, + ) -> Diag<'a, ()> { + let Self { + variant_def_id, + item_def_id, + span, + segment_ident, + bound_def_id, + self_ty, + tcx, + mode, + } = self; + let mut lint = Diag::new(dcx, level, "ambiguous associated item"); + + let mut could_refer_to = |kind: DefKind, def_id, also| { + let note_msg = format!( + "`{}` could{} refer to the {} defined here", + segment_ident, + also, + tcx.def_kind_descr(kind, def_id) + ); + lint.span_note(tcx.def_span(def_id), note_msg); + }; + + could_refer_to(DefKind::Variant, variant_def_id, ""); + could_refer_to(mode.def_kind_for_diagnostics(), item_def_id, " also"); + + lint.span_suggestion( + span, + "use fully-qualified syntax", + format!("<{} as {}>::{}", self_ty, tcx.item_name(bound_def_id), segment_ident), + Applicability::MachineApplicable, + ); + lint + } +} + pub(crate) fn assoc_tag_str(assoc_tag: ty::AssocTag) -> &'static str { match assoc_tag { ty::AssocTag::Fn => "function", diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 89bf398a9d166..e7071861d2ab0 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -26,7 +26,7 @@ use rustc_ast::LitKind; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, FatalError, Level, StashKey, + Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, FatalError, StashKey, struct_span_code_err, }; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; @@ -1549,54 +1549,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { span: Span, mode: LowerTypeRelativePathMode, ) -> Result, ErrorGuaranteed> { - struct AmbiguousAssocItem<'tcx> { - variant_def_id: DefId, - item_def_id: DefId, - span: Span, - segment_ident: Ident, - bound_def_id: DefId, - self_ty: Ty<'tcx>, - tcx: TyCtxt<'tcx>, - mode: LowerTypeRelativePathMode, - } - - impl<'a, 'tcx> Diagnostic<'a, ()> for AmbiguousAssocItem<'tcx> { - fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { - let Self { - variant_def_id, - item_def_id, - span, - segment_ident, - bound_def_id, - self_ty, - tcx, - mode, - } = self; - let mut lint = Diag::new(dcx, level, "ambiguous associated item"); - - let mut could_refer_to = |kind: DefKind, def_id, also| { - let note_msg = format!( - "`{}` could{} refer to the {} defined here", - segment_ident, - also, - tcx.def_kind_descr(kind, def_id) - ); - lint.span_note(tcx.def_span(def_id), note_msg); - }; - - could_refer_to(DefKind::Variant, variant_def_id, ""); - could_refer_to(mode.def_kind_for_diagnostics(), item_def_id, " also"); - - lint.span_suggestion( - span, - "use fully-qualified syntax", - format!("<{} as {}>::{}", self_ty, tcx.item_name(bound_def_id), segment_ident), - Applicability::MachineApplicable, - ); - lint - } - } - debug!(%self_ty, ?segment.ident); let tcx = self.tcx(); @@ -1676,7 +1628,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { AMBIGUOUS_ASSOCIATED_ITEMS, qpath_hir_id, span, - AmbiguousAssocItem { + errors::AmbiguityBetweenVariantAndAssocItem { variant_def_id, item_def_id, span, From fd36a801b1ea39cc9a251058aa231de09d844f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 29 May 2026 16:27:03 +0200 Subject: [PATCH 11/14] Move distractingly lengthy error reporting code into new `report_ambiguous_assoc_item` Moreover, in `probe_single_bound_for_assoc_item` unconditionally return an `Err(_)` if there's more than a single bound (aka ambiguity) instead of returning the first bound in some cases. This avoids triggering a debug assertion later on ("not enough bound vars"). See the added test for details. --- .../src/hir_ty_lowering/errors.rs | 139 ++++++++++++++++- .../src/hir_ty_lowering/mod.rs | 141 ++---------------- tests/crashes/139387.rs | 15 -- .../path-ambiguous-late-bound-vars.rs | 31 ++++ .../path-ambiguous-late-bound-vars.stderr | 26 ++++ 5 files changed, 205 insertions(+), 147 deletions(-) delete mode 100644 tests/crashes/139387.rs create mode 100644 tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.rs create mode 100644 tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 710d527146a53..b8cdd67519bef 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -403,6 +403,141 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) } + pub(super) fn report_ambiguous_assoc_item( + &self, + bound1: ty::PolyTraitRef<'tcx>, + bound2: ty::PolyTraitRef<'tcx>, + matching_candidates: impl Iterator>, + qself: AssocItemQSelf, + assoc_tag: ty::AssocTag, + assoc_ident: Ident, + span: Span, + constraint: Option<&hir::AssocItemConstraint<'tcx>>, + ) -> ErrorGuaranteed { + let tcx = self.tcx(); + + let assoc_kind_str = assoc_tag_str(assoc_tag); + let qself_str = qself.to_string(tcx); + let mut err = self.dcx().create_err(crate::errors::AmbiguousAssocItem { + span, + assoc_kind: assoc_kind_str, + assoc_ident, + qself: &qself_str, + }); + // Provide a more specific error code index entry for equality bindings. + err.code( + if let Some(constraint) = constraint + && let hir::AssocItemConstraintKind::Equality { .. } = constraint.kind + { + E0222 + } else { + E0221 + }, + ); + + // FIXME(#97583): Print associated item bindings properly (i.e., not as equality + // predicates!). + // FIXME: Turn this into a structured, translatable & more actionable suggestion. + let mut where_bounds = vec![]; + for bound in [bound1, bound2].into_iter().chain(matching_candidates) { + let bound_id = bound.def_id(); + let assoc_item = tcx.associated_items(bound_id).find_by_ident_and_kind( + tcx, + assoc_ident, + assoc_tag, + bound_id, + ); + let bound_span = assoc_item.and_then(|item| tcx.hir_span_if_local(item.def_id)); + + if let Some(bound_span) = bound_span { + err.span_label( + bound_span, + format!("ambiguous `{assoc_ident}` from `{}`", bound.print_trait_sugared(),), + ); + if let Some(constraint) = constraint { + match constraint.kind { + hir::AssocItemConstraintKind::Equality { term } => { + let term: ty::Term<'_> = match term { + hir::Term::Ty(ty) => self.lower_ty(ty).into(), + hir::Term::Const(ct) => { + let assoc_item = + assoc_item.expect("assoc_item should be present"); + let projection_term = bound.map_bound(|trait_ref| { + let item_segment = hir::PathSegment { + ident: constraint.ident, + hir_id: constraint.hir_id, + res: Res::Err, + args: Some(constraint.gen_args), + infer_args: false, + }; + + let alias_args = self.lower_generic_args_of_assoc_item( + constraint.ident.span, + assoc_item.def_id, + &item_segment, + trait_ref.args, + ); + ty::AliasTerm::new_from_def_id( + tcx, + assoc_item.def_id, + alias_args, + ) + }); + + // FIXME(mgca): code duplication with other places we lower + // the rhs' of associated const bindings + let ty = projection_term.map_bound(|alias| { + tcx.type_of(alias.def_id()) + .instantiate(tcx, alias.args) + .skip_norm_wip() + }); + let ty = super::bounds::check_assoc_const_binding_type( + self, + constraint.ident, + ty, + constraint.hir_id, + ); + + self.lower_const_arg(ct, ty).into() + } + }; + if term.references_error() { + continue; + } + // FIXME(#97583): This isn't syntactically well-formed! + where_bounds.push(format!( + " T: {trait}::{assoc_ident} = {term}", + trait = bound.print_only_trait_path(), + )); + } + // FIXME: Provide a suggestion. + hir::AssocItemConstraintKind::Bound { bounds: _ } => {} + } + } else { + err.span_suggestion_verbose( + span.with_hi(assoc_ident.span.lo()), + "use fully-qualified syntax to disambiguate", + format!("<{qself_str} as {}>::", bound.print_only_trait_path()), + Applicability::MaybeIncorrect, + ); + } + } else { + let trait_ = tcx.short_string(bound.print_only_trait_path(), err.long_ty_path()); + err.note(format!( + "associated {assoc_kind_str} `{assoc_ident}` could derive from `{trait_}`", + )); + } + } + if !where_bounds.is_empty() { + err.help(format!( + "consider introducing a new type parameter `T` and adding `where` constraints:\ + \n where\n T: {qself_str},\n{}", + where_bounds.join(",\n"), + )); + } + err.emit() + } + pub(crate) fn report_missing_self_ty_for_resolved_path( &self, trait_def_id: DefId, @@ -568,7 +703,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } - pub(super) fn report_ambiguous_assoc_item_path( + fn report_ambiguous_assoc_item_path( &self, span: Span, types: &[String], @@ -1899,7 +2034,7 @@ impl<'a, 'tcx> rustc_errors::Diagnostic<'a, ()> for AmbiguityBetweenVariantAndAs } } -pub(crate) fn assoc_tag_str(assoc_tag: ty::AssocTag) -> &'static str { +fn assoc_tag_str(assoc_tag: ty::AssocTag) -> &'static str { match assoc_tag { ty::AssocTag::Fn => "function", ty::AssocTag::Const => "constant", diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index e7071861d2ab0..d2c236b5e0a12 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -36,7 +36,6 @@ use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::DynCompatibilityViolation; use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::middle::stability::AllowUnstable; -use rustc_middle::ty::print::PrintPolyTraitRefExt as _; use rustc_middle::ty::{ self, Const, FnSigKind, GenericArgKind, GenericArgsRef, GenericParamDefKind, LitToConstInput, Ty, TyCtxt, TypeSuperFoldable, TypeVisitableExt, TypingMode, Unnormalized, Upcast, @@ -1281,13 +1280,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { where I: Iterator>, { - let tcx = self.tcx(); - let mut matching_candidates = all_candidates().filter(|r| { self.probe_trait_that_defines_assoc_item(r.def_id(), assoc_tag, assoc_ident) }); - let Some(bound) = matching_candidates.next() else { + let Some(bound1) = matching_candidates.next() else { return Err(self.report_unresolved_assoc_item( all_candidates, qself, @@ -1297,137 +1294,21 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { constraint, )); }; - debug!(?bound); if let Some(bound2) = matching_candidates.next() { - debug!(?bound2); - - let assoc_kind_str = errors::assoc_tag_str(assoc_tag); - let qself_str = qself.to_string(tcx); - let mut err = self.dcx().create_err(crate::errors::AmbiguousAssocItem { - span, - assoc_kind: assoc_kind_str, + return Err(self.report_ambiguous_assoc_item( + bound1, + bound2, + matching_candidates, + qself, + assoc_tag, assoc_ident, - qself: &qself_str, - }); - // Provide a more specific error code index entry for equality bindings. - err.code( - if let Some(constraint) = constraint - && let hir::AssocItemConstraintKind::Equality { .. } = constraint.kind - { - E0222 - } else { - E0221 - }, - ); - - // FIXME(#97583): Print associated item bindings properly (i.e., not as equality - // predicates!). - // FIXME: Turn this into a structured, translatable & more actionable suggestion. - let mut where_bounds = vec![]; - for bound in [bound, bound2].into_iter().chain(matching_candidates) { - let bound_id = bound.def_id(); - let assoc_item = tcx.associated_items(bound_id).find_by_ident_and_kind( - tcx, - assoc_ident, - assoc_tag, - bound_id, - ); - let bound_span = assoc_item.and_then(|item| tcx.hir_span_if_local(item.def_id)); - - if let Some(bound_span) = bound_span { - err.span_label( - bound_span, - format!("ambiguous `{assoc_ident}` from `{}`", bound.print_trait_sugared(),), - ); - if let Some(constraint) = constraint { - match constraint.kind { - hir::AssocItemConstraintKind::Equality { term } => { - let term: ty::Term<'_> = match term { - hir::Term::Ty(ty) => self.lower_ty(ty).into(), - hir::Term::Const(ct) => { - let assoc_item = - assoc_item.expect("assoc_item should be present"); - let projection_term = bound.map_bound(|trait_ref| { - let item_segment = hir::PathSegment { - ident: constraint.ident, - hir_id: constraint.hir_id, - res: Res::Err, - args: Some(constraint.gen_args), - infer_args: false, - }; - - let alias_args = self.lower_generic_args_of_assoc_item( - constraint.ident.span, - assoc_item.def_id, - &item_segment, - trait_ref.args, - ); - ty::AliasTerm::new_from_def_id( - tcx, - assoc_item.def_id, - alias_args, - ) - }); - - // FIXME(mgca): code duplication with other places we lower - // the rhs' of associated const bindings - let ty = projection_term.map_bound(|alias| { - tcx.type_of(alias.def_id()) - .instantiate(tcx, alias.args) - .skip_norm_wip() - }); - let ty = bounds::check_assoc_const_binding_type( - self, - constraint.ident, - ty, - constraint.hir_id, - ); - - self.lower_const_arg(ct, ty).into() - } - }; - if term.references_error() { - continue; - } - // FIXME(#97583): This isn't syntactically well-formed! - where_bounds.push(format!( - " T: {trait}::{assoc_ident} = {term}", - trait = bound.print_only_trait_path(), - )); - } - // FIXME: Provide a suggestion. - hir::AssocItemConstraintKind::Bound { bounds: _ } => {} - } - } else { - err.span_suggestion_verbose( - span.with_hi(assoc_ident.span.lo()), - "use fully-qualified syntax to disambiguate", - format!("<{qself_str} as {}>::", bound.print_only_trait_path()), - Applicability::MaybeIncorrect, - ); - } - } else { - let trait_ = - tcx.short_string(bound.print_only_trait_path(), err.long_ty_path()); - err.note(format!( - "associated {assoc_kind_str} `{assoc_ident}` could derive from `{trait_}`", - )); - } - } - if !where_bounds.is_empty() { - err.help(format!( - "consider introducing a new type parameter `T` and adding `where` constraints:\ - \n where\n T: {qself_str},\n{}", - where_bounds.join(",\n"), - )); - let reported = err.emit(); - return Err(reported); - } - err.emit(); + span, + constraint, + )); } - Ok(bound) + Ok(bound1) } /// Lower a [type-relative](hir::QPath::TypeRelative) path in type position to a type. diff --git a/tests/crashes/139387.rs b/tests/crashes/139387.rs deleted file mode 100644 index 133643ad084ba..0000000000000 --- a/tests/crashes/139387.rs +++ /dev/null @@ -1,15 +0,0 @@ -//@ known-bug: #139387 -//@ needs-rustc-debug-assertions - -trait A { - fn method() -> impl Sized; -} -trait B { - fn method(Hash: Wrap Epsilon<'_, SI1: Eta>>>) -> impl Sized; -} - -fn ambiguous() -where - T::method(..): Send, -{ -} diff --git a/tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.rs b/tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.rs new file mode 100644 index 0000000000000..d994e26ad03a9 --- /dev/null +++ b/tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.rs @@ -0,0 +1,31 @@ +// We used to lower the ambiguous `T::f(..)` to `::f::{type#0}` after emitting the error. +// Meaning we picked one of the candidates and proceeded instead of bailing out early. +// However, sensibly RBV doesn't register any bound vars for ambiguous RTN[^1], so later on when +// wrapping the predicate (here: WellFormed) into a Binder we would correctly fail bound var +// validation (in debug mode). +// +// We now bail out early and thus prevent nonsensical types from getting leaked to subsequent +// compiler passes. +// +// [^1]: It actually maintains its own bespoke lowering function for type-relative paths that +// relatively closely mirrors the one in HIR ty lowering. + +// issue: +//@ needs-rustc-debug-assertions +#![feature(return_type_notation)] + +trait A { + fn f() -> impl Sized; +} + +trait B { + fn f<'b>() -> impl Sized; +} + +fn f() +where + T::f(..):, //~ ERROR ambiguous associated function +{ +} + +fn main() {} diff --git a/tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.stderr b/tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.stderr new file mode 100644 index 0000000000000..5b6804095de0d --- /dev/null +++ b/tests/ui/associated-type-bounds/return-type-notation/path-ambiguous-late-bound-vars.stderr @@ -0,0 +1,26 @@ +error[E0221]: ambiguous associated function `f` in bounds of `T` + --> $DIR/path-ambiguous-late-bound-vars.rs:27:5 + | +LL | fn f() -> impl Sized; + | --------------------- ambiguous `f` from `A` +... +LL | fn f<'b>() -> impl Sized; + | ------------------------- ambiguous `f` from `B` +... +LL | T::f(..):, + | ^^^^^^^^ ambiguous associated function `f` + | +help: use fully-qualified syntax to disambiguate + | +LL - T::f(..):, +LL + ::f(..):, + | +help: use fully-qualified syntax to disambiguate + | +LL - T::f(..):, +LL + ::f(..):, + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0221`. From 4fe62be7fe8a732d91cda4ff08a85ba2e713366f Mon Sep 17 00:00:00 2001 From: Andrii Anoshyn Date: Fri, 29 May 2026 22:37:53 +0300 Subject: [PATCH 12/14] Address irrefutable while let diagnostic review --- compiler/rustc_hir_typeck/src/coercion.rs | 6 +++--- tests/ui/coercion/coerce-loop-issue-122561.stderr | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index d8881de576eb4..9a20d47bda9af 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1895,11 +1895,11 @@ impl<'tcx> CoerceMany<'tcx> { err.note(format!("{loop_type} evaluate to unit type `()`")); if loop_src == hir::LoopSource::While - && let Some(pat) = irrefutable_while_let_pattern(block) + && let Some(pat) = irrefutable_if_let_expr(block) { err.span_note( pat.span, - "this pattern always matches, so the loop condition never fails", + "this pattern always matches, consider using `loop` instead", ); } } @@ -2134,7 +2134,7 @@ impl<'tcx> CoerceMany<'tcx> { } } -fn irrefutable_while_let_pattern<'hir>(block: &hir::Block<'hir>) -> Option<&'hir hir::Pat<'hir>> { +fn irrefutable_if_let_expr<'hir>(block: &hir::Block<'hir>) -> Option<&'hir hir::Pat<'hir>> { let hir::ExprKind::If(cond, _, _) = block.expr?.kind else { return None; }; diff --git a/tests/ui/coercion/coerce-loop-issue-122561.stderr b/tests/ui/coercion/coerce-loop-issue-122561.stderr index ef7a3049465ad..1051823e6c01f 100644 --- a/tests/ui/coercion/coerce-loop-issue-122561.stderr +++ b/tests/ui/coercion/coerce-loop-issue-122561.stderr @@ -182,7 +182,7 @@ LL | | } | |_____^ expected `bool`, found `()` | = note: `while` loops evaluate to unit type `()` -note: this pattern always matches, so the loop condition never fails +note: this pattern always matches, consider using `loop` instead --> $DIR/coerce-loop-issue-122561.rs:73:15 | LL | while let x = false { @@ -207,7 +207,7 @@ LL | | } | |_____^ expected `bool`, found `()` | = note: `while` loops evaluate to unit type `()` -note: this pattern always matches, so the loop condition never fails +note: this pattern always matches, consider using `loop` instead --> $DIR/coerce-loop-issue-122561.rs:82:15 | LL | while let (x, _) = (false, true) { From ad7cda0ebd0129cf580ba1a7b831715509b3c005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Fri, 6 Mar 2026 15:33:37 +0100 Subject: [PATCH 13/14] Use `trait_object_dummy_self` more & heavily fix+update related docs --- .../src/outlives/implicit_infer.rs | 117 +++++++----------- compiler/rustc_middle/src/ty/context.rs | 30 ++++- compiler/rustc_middle/src/ty/print/pretty.rs | 6 +- compiler/rustc_symbol_mangling/src/v0.rs | 8 +- compiler/rustc_type_ir/src/predicate.rs | 31 ++--- 5 files changed, 93 insertions(+), 99 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs index 23e6b2281b370..174f702dcd0c4 100644 --- a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs +++ b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs @@ -8,11 +8,7 @@ use tracing::debug; use super::explicit::ExplicitPredicatesMap; use super::utils::*; -/// Infer predicates for the items in the crate. -/// -/// `global_inferred_outlives`: this is initially the empty map that -/// was generated by walking the items in the crate. This will -/// now be filled with inferred predicates. +/// Infer outlives-predicates for the items in the local crate. pub(super) fn infer_predicates( tcx: TyCtxt<'_>, ) -> FxIndexMap>> { @@ -154,7 +150,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( args, required_predicates, explicit_map, - None, + IgnorePredicatesReferencingSelf::No, ); } @@ -175,7 +171,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( args, required_predicates, explicit_map, - None, + IgnorePredicatesReferencingSelf::No, ); } @@ -183,23 +179,27 @@ fn insert_required_predicates_to_be_wf<'tcx>( // This corresponds to `dyn Trait<..>`. In this case, we should // use the explicit predicates as well. debug!("Dynamic"); - if let Some(ex_trait_ref) = obj.principal() { - // Here, we are passing the type `usize` as a - // placeholder value with the function - // `with_self_ty`, since there is no concrete type - // `Self` for a `dyn Trait` at this - // stage. Therefore when checking explicit - // predicates in `check_explicit_predicates` we - // need to ignore checking the explicit_map for - // Self type. - let args = ex_trait_ref.with_self_ty(tcx, tcx.types.usize).skip_binder().args; + if let Some(trait_ref) = obj.principal() { + let args = trait_ref + .with_self_ty(tcx, tcx.types.trait_object_dummy_self) + .skip_binder() + .args; + // We skip predicates that reference the `Self` type parameter since we don't + // want to leak the dummy Self to the predicates map. + // + // While filtering out bounds like `Self: 'a` as in `trait Trait<'a, T>: 'a {}` + // doesn't matter since they can't affect the lifetime / type parameters anyway, + // for bounds like `Self::AssocTy: 'b` which we of course currently also ignore + // (see also #54467) it might conceivably be better to extract the binding + // `AssocTy = U` from the trait object type (which must exist) and thus infer + // an outlives requirement that `U: 'b`. check_explicit_predicates( tcx, - ex_trait_ref.skip_binder().def_id, + trait_ref.def_id(), args, required_predicates, explicit_map, - Some(tcx.types.self_param), + IgnorePredicatesReferencingSelf::Yes, ); } } @@ -215,7 +215,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( args, required_predicates, explicit_map, - None, + IgnorePredicatesReferencingSelf::No, ); } @@ -244,77 +244,44 @@ fn insert_required_predicates_to_be_wf<'tcx>( /// will give us `U: 'static` and `U: Outer`. The latter we /// can ignore, but we will want to process `U: 'static`, /// applying the instantiation as above. +#[tracing::instrument(level = "debug", skip(tcx))] fn check_explicit_predicates<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, args: &[GenericArg<'tcx>], required_predicates: &mut RequiredPredicates<'tcx>, explicit_map: &mut ExplicitPredicatesMap<'tcx>, - ignored_self_ty: Option>, + ignore_preds_refing_self: IgnorePredicatesReferencingSelf, ) { - debug!( - "check_explicit_predicates(def_id={:?}, \ - args={:?}, \ - explicit_map={:?}, \ - required_predicates={:?}, \ - ignored_self_ty={:?})", - def_id, args, explicit_map, required_predicates, ignored_self_ty, - ); let explicit_predicates = explicit_map.explicit_predicates_of(tcx, def_id); - for (outlives_predicate, &span) in explicit_predicates.as_ref().skip_binder() { - debug!("outlives_predicate = {outlives_predicate:?}"); - - // Careful: If we are inferring the effects of a `dyn Trait<..>` - // type, then when we look up the predicates for `Trait`, - // we may find some that reference `Self`. e.g., perhaps the - // definition of `Trait` was: - // - // ``` - // trait Trait<'a, T> where Self: 'a { .. } - // ``` - // - // we want to ignore such predicates here, because - // there is no type parameter for them to affect. Consider - // a struct containing `dyn Trait`: - // - // ``` - // struct MyStruct<'x, X> { field: Box> } - // ``` - // - // The `where Self: 'a` predicate refers to the *existential, hidden type* - // that is represented by the `dyn Trait`, not to the `X` type parameter - // (or any other generic parameter) declared on `MyStruct`. - // - // Note that we do this check for self **before** applying `args`. In the - // case that `args` come from a `dyn Trait` type, our caller will have - // included `Self = usize` as the value for `Self`. If we were - // to apply the args, and not filter this predicate, we might then falsely - // conclude that e.g., `X: 'x` was a reasonable inferred requirement. - // - // Another similar case is where we have an inferred - // requirement like `::Foo: 'b`. We presently - // ignore such requirements as well (cc #54467)-- though - // conceivably it might be better if we could extract the `Foo - // = X` binding from the object type (there must be such a - // binding) and thus infer an outlives requirement that `X: - // 'b`. - if let Some(self_ty) = ignored_self_ty - && let GenericArgKind::Type(ty) = outlives_predicate.0.kind() - && ty.walk().any(|arg| arg == self_ty.into()) + for (&predicate @ ty::OutlivesPredicate(arg, _), &span) in + explicit_predicates.as_ref().skip_binder() + { + debug!(?predicate); + + if let IgnorePredicatesReferencingSelf::Yes = ignore_preds_refing_self + && arg.walk().any(|arg| arg == tcx.types.self_param.into()) { - debug!("skipping self ty = {ty:?}"); + debug!("ignoring predicate since it references `Self`"); continue; } - let predicate = - explicit_predicates.rebind(*outlives_predicate).instantiate(tcx, args).skip_norm_wip(); - debug!("predicate = {predicate:?}"); - insert_outlives_predicate(tcx, predicate.0, predicate.1, span, required_predicates); + let predicate @ ty::OutlivesPredicate(arg, region) = + explicit_predicates.rebind(predicate).instantiate(tcx, args).skip_norm_wip(); + debug!(?predicate); + + insert_outlives_predicate(tcx, arg, region, span, required_predicates); } } -/// Check the inferred predicates declared on the type. +#[derive(Debug)] +enum IgnorePredicatesReferencingSelf { + Yes, + No, +} + +/// Check the inferred predicates of the type. /// /// ### Example /// diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e756277b92d76..d14e10c5014ad 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -324,10 +324,32 @@ pub struct CommonTypes<'tcx> { pub never: Ty<'tcx>, pub self_param: Ty<'tcx>, - /// Dummy type used for the `Self` of a `TraitRef` created for converting - /// a trait object, and which gets removed in `ExistentialTraitRef`. - /// This type must not appear anywhere in other converted types. - /// `Infer(ty::FreshTy(0))` does the job. + /// A dummy type that can be used as the self type of trait object types outside of + /// [`ty::ExistentialTraitRef`], [`ty::ExistentialProjection`], etc. + /// + /// This is most useful or even necessary when you want to manipulate existential predicates + /// together with normal predicates or if you want to pass them to an API that only expects + /// normal predicates. + /// + /// Indeed, you can sometimes use the trait object type itself as the self type instead of this + /// dummy type. However, that's not always correct: For example, if said trait object type can + /// also appear "naturally" in whatever type system entity you're working with (like predicates) + /// but you still need to be able to identify the erased self type later on. + /// That's when this dummy type comes in handy. + /// + /// HIR ty lowering guarantees / has to guarantee that this dummy type doesn't appear in the + /// lowered types, so you can "freely" use it (see warning below). + /// + ///
+ /// + /// Under the hood, this type is just `ty::Infer(ty::FreshTy(0))`. Consequently, you must be + /// sure that fresh types cannot appear by other means in whatever type system entity you're + /// working with. + /// + /// Keep uses of this dummy type as local as possible and try not to leak it to subsequent + /// passes! + /// + ///
pub trait_object_dummy_self: Ty<'tcx>, /// Pre-interned `Infer(ty::TyVar(n))` for small values of `n`. diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index daead99b977c1..026b6a09a8bb7 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -3265,9 +3265,9 @@ define_print! { } ty::ExistentialTraitRef<'tcx> { - // Use a type that can't appear in defaults of type parameters. - let dummy_self = Ty::new_fresh(p.tcx(), 0); - let trait_ref = self.with_self_ty(p.tcx(), dummy_self); + // Dummy Self is safe to use as it can't appear in generic param defaults which is important + // later on for correctly eliding generic args that coincide with their default. + let trait_ref = self.with_self_ty(p.tcx(), p.tcx().types.trait_object_dummy_self); trait_ref.print_only_trait_path().print(p)?; } diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 61c1c83c3f8f6..a9573cdf1bf75 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -646,9 +646,11 @@ impl<'tcx> Printer<'tcx> for V0SymbolMangler<'tcx> { // could have different bound vars *anyways*. match predicate.as_ref().skip_binder() { ty::ExistentialPredicate::Trait(trait_ref) => { - // Use a type that can't appear in defaults of type parameters. - let dummy_self = Ty::new_fresh(p.tcx, 0); - let trait_ref = trait_ref.with_self_ty(p.tcx, dummy_self); + // Dummy Self is safe to use as it can't appear in generic param defaults + // which is important later on for correctly eliding generic args that + // coincide with their default. + let trait_ref = + trait_ref.with_self_ty(p.tcx, p.tcx.types.trait_object_dummy_self); p.print_def_path(trait_ref.def_id, trait_ref.args)?; } ty::ExistentialPredicate::Projection(projection) => { diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 301cf7dbf1087..e220676aef29c 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -388,14 +388,13 @@ impl ty::Binder> { } } -/// An existential reference to a trait, where `Self` is erased. +/// An existential reference to a trait where the self type `Self` is erased. /// -/// For example, the trait object `Trait<'a, 'b, X, Y>` is: +/// For example, the trait object type `Trait<'a, T, N>` can be understood as: /// ```ignore (illustrative) -/// exists T. T: Trait<'a, 'b, X, Y> +/// exists X: Trait<'a, T, N> /// ``` -/// The generic parameters don't include the erased `Self`, only trait -/// type and lifetime parameters (`[X, Y]` and `['a, 'b]` above). +/// The generic arguments don't include the erased self type (so it's only `['a, T, N]`). #[derive_where(Clone, Copy, Hash, PartialEq; I: Interner)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( @@ -438,13 +437,18 @@ impl ExistentialTraitRef { } } - /// Object types don't have a self type specified. Therefore, when - /// we convert the principal trait-ref into a normal trait-ref, - /// you must give *some* self type. A common choice is `mk_err()` - /// or some placeholder type. + /// Convert the *existential* trait ref into a normal one by providing a self type. + /// + /// Existential trait refs don't contain a self type, it's erased. + /// Therefore, you must specify *some* self type to perform the conversion. + /// A common choice is the trait object type itself or some kind of dummy type. pub fn with_self_ty(self, interner: I, self_ty: I::Ty) -> TraitRef { + // FIXME(#157122): This assertion was accidentally commented out in refactoring PR #53816 + // back in 2018 but nowadays it can actually trigger. Either remove this + // comment entirely if the assertion is incorrect or uncomment it and fix + // the fallout! // otherwise the escaping vars would be captured by the binder - // debug_assert!(!self_ty.has_escaping_bound_vars()); + //debug_assert!(!self_ty.has_escaping_bound_vars()); TraitRef::new(interner, self.def_id, [self_ty.into()].into_iter().chain(self.args.iter())) } @@ -455,10 +459,9 @@ impl ty::Binder> { self.skip_binder().def_id } - /// Object types don't have a self type specified. Therefore, when - /// we convert the principal trait-ref into a normal trait-ref, - /// you must give *some* self type. A common choice is `mk_err()` - /// or some placeholder type. + /// Convert the *existential* polymorphic trait ref into a normal one by providing a self type. + /// + /// See also [`ExistentialTraitRef::with_self_ty`]. pub fn with_self_ty(&self, cx: I, self_ty: I::Ty) -> ty::Binder> { self.map_bound(|trait_ref| trait_ref.with_self_ty(cx, self_ty)) } From 57393b76a774b6ee9b1e09e68dbb9038a8ee9010 Mon Sep 17 00:00:00 2001 From: Andrii Anoshyn Date: Fri, 29 May 2026 23:38:26 +0300 Subject: [PATCH 14/14] Label irrefutable while let pattern diagnostic --- compiler/rustc_hir_typeck/src/coercion.rs | 2 +- .../coercion/coerce-loop-issue-122561.stderr | 20 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 9a20d47bda9af..2b47b9fc9f093 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1897,7 +1897,7 @@ impl<'tcx> CoerceMany<'tcx> { if loop_src == hir::LoopSource::While && let Some(pat) = irrefutable_if_let_expr(block) { - err.span_note( + err.span_label( pat.span, "this pattern always matches, consider using `loop` instead", ); diff --git a/tests/ui/coercion/coerce-loop-issue-122561.stderr b/tests/ui/coercion/coerce-loop-issue-122561.stderr index 1051823e6c01f..da69098dfe04a 100644 --- a/tests/ui/coercion/coerce-loop-issue-122561.stderr +++ b/tests/ui/coercion/coerce-loop-issue-122561.stderr @@ -173,7 +173,10 @@ error[E0308]: mismatched types | LL | fn while_let_binding() -> bool { | ---- expected `bool` because of return type -LL | / while let x = false { +LL | while let x = false { + | ^ - this pattern always matches, consider using `loop` instead + | _____| + | | LL | | LL | | if x { LL | | return true; @@ -182,11 +185,6 @@ LL | | } | |_____^ expected `bool`, found `()` | = note: `while` loops evaluate to unit type `()` -note: this pattern always matches, consider using `loop` instead - --> $DIR/coerce-loop-issue-122561.rs:73:15 - | -LL | while let x = false { - | ^ help: consider returning a value here | LL ~ } @@ -198,7 +196,10 @@ error[E0308]: mismatched types | LL | fn while_let_tuple() -> bool { | ---- expected `bool` because of return type -LL | / while let (x, _) = (false, true) { +LL | while let (x, _) = (false, true) { + | ^ ------ this pattern always matches, consider using `loop` instead + | _____| + | | LL | | LL | | if x { LL | | return true; @@ -207,11 +208,6 @@ LL | | } | |_____^ expected `bool`, found `()` | = note: `while` loops evaluate to unit type `()` -note: this pattern always matches, consider using `loop` instead - --> $DIR/coerce-loop-issue-122561.rs:82:15 - | -LL | while let (x, _) = (false, true) { - | ^^^^^^ help: consider returning a value here | LL ~ }