From 744c670812245dc50291e5121341ca2ca0a51fe8 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sun, 19 Oct 2025 01:06:56 +0000 Subject: [PATCH 1/2] Add built-in `const` impls for `Clone` and `Copy` --- .../src/solve/effect_goals.rs | 28 +++++- .../src/traits/effects.rs | 94 ++++++++++++++++++- compiler/rustc_type_ir/src/predicate.rs | 2 +- .../traits/const-traits/const-traits-alloc.rs | 2 +- .../traits/const-traits/const-traits-core.rs | 15 ++- 5 files changed, 133 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index 48bd0963c8743..695a29e58a5e7 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -211,10 +211,32 @@ where } fn consider_builtin_copy_clone_candidate( - _ecx: &mut EvalCtxt<'_, D>, - _goal: Goal, + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, ) -> Result, NoSolution> { - Err(NoSolution) + let cx = ecx.cx(); + + let self_ty = goal.predicate.self_ty(); + let constituent_tys = + structural_traits::instantiate_constituent_tys_for_copy_clone_trait(ecx, self_ty)?; + + ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + ecx.enter_forall(constituent_tys, |ecx, tys| { + ecx.add_goals( + GoalSource::ImplWhereBound, + tys.into_iter().map(|ty| { + goal.with( + cx, + ty::ClauseKind::HostEffect( + goal.predicate.with_replaced_self_ty(cx, ty), + ), + ) + }), + ); + }); + + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) } fn consider_builtin_fn_ptr_trait_candidate( diff --git a/compiler/rustc_trait_selection/src/traits/effects.rs b/compiler/rustc_trait_selection/src/traits/effects.rs index 7f2a9999d6464..9ca97fb06d5da 100644 --- a/compiler/rustc_trait_selection/src/traits/effects.rs +++ b/compiler/rustc_trait_selection/src/traits/effects.rs @@ -8,7 +8,7 @@ use rustc_middle::span_bug; use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::elaborate::elaborate; use rustc_middle::ty::fast_reject::DeepRejectCtxt; -use rustc_middle::ty::{self, TypingMode}; +use rustc_middle::ty::{self, Ty, TypingMode}; use thin_vec::{ThinVec, thin_vec}; use super::SelectionContext; @@ -303,6 +303,9 @@ fn evaluate_host_effect_from_builtin_impls<'tcx>( obligation: &HostEffectObligation<'tcx>, ) -> Result>, EvaluationFailure> { match selcx.tcx().as_lang_item(obligation.predicate.def_id()) { + Some(LangItem::Copy | LangItem::Clone) => { + evaluate_host_effect_for_copy_clone_goal(selcx, obligation) + } Some(LangItem::Destruct) => evaluate_host_effect_for_destruct_goal(selcx, obligation), Some(LangItem::Fn | LangItem::FnMut | LangItem::FnOnce) => { evaluate_host_effect_for_fn_goal(selcx, obligation) @@ -311,6 +314,95 @@ fn evaluate_host_effect_from_builtin_impls<'tcx>( } } +fn evaluate_host_effect_for_copy_clone_goal<'tcx>( + selcx: &mut SelectionContext<'_, 'tcx>, + obligation: &HostEffectObligation<'tcx>, +) -> Result>, EvaluationFailure> { + let tcx = selcx.tcx(); + let self_ty = obligation.predicate.self_ty(); + let constituent_tys = match *self_ty.kind() { + // impl Copy/Clone for FnDef, FnPtr + ty::FnDef(..) | ty::FnPtr(..) | ty::Error(_) => Ok(ty::Binder::dummy(vec![])), + + // Implementations are provided in core + ty::Uint(_) + | ty::Int(_) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) + | ty::Bool + | ty::Float(_) + | ty::Char + | ty::RawPtr(..) + | ty::Never + | ty::Ref(_, _, ty::Mutability::Not) + | ty::Array(..) => Err(EvaluationFailure::NoSolution), + + // Cannot implement in core, as we can't be generic over patterns yet, + // so we'd have to list all patterns and type combinations. + ty::Pat(ty, ..) => Ok(ty::Binder::dummy(vec![ty])), + + ty::Dynamic(..) + | ty::Str + | ty::Slice(_) + | ty::Foreign(..) + | ty::Ref(_, _, ty::Mutability::Mut) + | ty::Adt(_, _) + | ty::Alias(_, _) + | ty::Param(_) + | ty::Placeholder(..) => Err(EvaluationFailure::NoSolution), + + ty::Bound(..) + | ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + panic!("unexpected type `{self_ty:?}`") + } + + // impl Copy/Clone for (T1, T2, .., Tn) where T1: Copy/Clone, T2: Copy/Clone, .. Tn: Copy/Clone + ty::Tuple(tys) => Ok(ty::Binder::dummy(tys.to_vec())), + + // impl Copy/Clone for Closure where Self::TupledUpvars: Copy/Clone + ty::Closure(_, args) => Ok(ty::Binder::dummy(vec![args.as_closure().tupled_upvars_ty()])), + + // impl Copy/Clone for CoroutineClosure where Self::TupledUpvars: Copy/Clone + ty::CoroutineClosure(_, args) => { + Ok(ty::Binder::dummy(vec![args.as_coroutine_closure().tupled_upvars_ty()])) + } + + // only when `coroutine_clone` is enabled and the coroutine is movable + // impl Copy/Clone for Coroutine where T: Copy/Clone forall T in (upvars, witnesses) + ty::Coroutine(def_id, args) => match tcx.coroutine_movability(def_id) { + ty::Movability::Static => Err(EvaluationFailure::NoSolution), + ty::Movability::Movable => { + if tcx.features().coroutine_clone() { + Ok(ty::Binder::dummy(vec![ + args.as_coroutine().tupled_upvars_ty(), + Ty::new_coroutine_witness_for_coroutine(tcx, def_id, args), + ])) + } else { + Err(EvaluationFailure::NoSolution) + } + } + }, + + ty::UnsafeBinder(_) => Err(EvaluationFailure::NoSolution), + + // impl Copy/Clone for CoroutineWitness where T: Copy/Clone forall T in coroutine_hidden_types + ty::CoroutineWitness(def_id, args) => Ok(tcx + .coroutine_hidden_types(def_id) + .instantiate(tcx, args) + .map_bound(|bound| bound.types.to_vec())), + }?; + + Ok(constituent_tys + .iter() + .map(|ty| { + obligation.with( + tcx, + ty.map_bound(|ty| ty::TraitRef::new(tcx, obligation.predicate.def_id(), [ty])) + .to_host_effect_clause(tcx, obligation.predicate.constness), + ) + }) + .collect()) +} + // NOTE: Keep this in sync with `const_conditions_for_destruct` in the new solver. fn evaluate_host_effect_for_destruct_goal<'tcx>( selcx: &mut SelectionContext<'_, 'tcx>, diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 3e32a77885468..c7dc3fbc9a605 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -121,7 +121,7 @@ impl ty::Binder> { } pub fn to_host_effect_clause(self, cx: I, constness: BoundConstness) -> I::Clause { - self.map_bound(|trait_ref| { + self.map_bound(|trait_ref: TraitRef| { ty::ClauseKind::HostEffect(HostEffectPredicate { trait_ref, constness }) }) .upcast(cx) diff --git a/tests/ui/traits/const-traits/const-traits-alloc.rs b/tests/ui/traits/const-traits/const-traits-alloc.rs index 07725ef02f185..4dfec2f77f1ba 100644 --- a/tests/ui/traits/const-traits/const-traits-alloc.rs +++ b/tests/ui/traits/const-traits/const-traits-alloc.rs @@ -1,4 +1,4 @@ -//@ run-pass +//@ check-pass #![feature(const_trait_impl, const_default)] #![allow(dead_code)] // alloc::string diff --git a/tests/ui/traits/const-traits/const-traits-core.rs b/tests/ui/traits/const-traits/const-traits-core.rs index 6df53daae137f..2cafde4f5bd0f 100644 --- a/tests/ui/traits/const-traits/const-traits-core.rs +++ b/tests/ui/traits/const-traits/const-traits-core.rs @@ -1,6 +1,13 @@ -//@ run-pass +//@ check-pass #![feature( - const_trait_impl, const_default, ptr_alignment_type, ascii_char, f16, f128, sync_unsafe_cell, + const_clone, + const_default, + const_trait_impl, + ptr_alignment_type, + ascii_char, + f16, + f128, + sync_unsafe_cell, )] #![allow(dead_code)] // core::default @@ -43,4 +50,8 @@ const REF_CELL: std::cell::RefCell<()> = Default::default(); const UNSAFE_CELL: std::cell::UnsafeCell<()> = Default::default(); const SYNC_UNSAFE_CELL: std::cell::SyncUnsafeCell<()> = Default::default(); +// `Clone` for tuples +const BUILTIN_CLONE: () = ().clone(); +const BUILTIN_CLONE_2: (u32, i32) = (42, 100).clone(); + fn main() {} From 042018df2d7a98d24a4c5216b122fa2b8610242d Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sun, 26 Oct 2025 19:57:05 +0000 Subject: [PATCH 2/2] respond to review --- .../src/traits/effects.rs | 27 +++++++++++-------- .../src/traits/select/mod.rs | 2 +- compiler/rustc_type_ir/src/predicate.rs | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/effects.rs b/compiler/rustc_trait_selection/src/traits/effects.rs index 9ca97fb06d5da..c29e3281f2248 100644 --- a/compiler/rustc_trait_selection/src/traits/effects.rs +++ b/compiler/rustc_trait_selection/src/traits/effects.rs @@ -368,19 +368,24 @@ fn evaluate_host_effect_for_copy_clone_goal<'tcx>( // only when `coroutine_clone` is enabled and the coroutine is movable // impl Copy/Clone for Coroutine where T: Copy/Clone forall T in (upvars, witnesses) - ty::Coroutine(def_id, args) => match tcx.coroutine_movability(def_id) { - ty::Movability::Static => Err(EvaluationFailure::NoSolution), - ty::Movability::Movable => { - if tcx.features().coroutine_clone() { - Ok(ty::Binder::dummy(vec![ - args.as_coroutine().tupled_upvars_ty(), - Ty::new_coroutine_witness_for_coroutine(tcx, def_id, args), - ])) - } else { - Err(EvaluationFailure::NoSolution) + ty::Coroutine(def_id, args) => { + if selcx.should_stall_coroutine(def_id) { + return Err(EvaluationFailure::Ambiguous); + } + match tcx.coroutine_movability(def_id) { + ty::Movability::Static => Err(EvaluationFailure::NoSolution), + ty::Movability::Movable => { + if tcx.features().coroutine_clone() { + Ok(ty::Binder::dummy(vec![ + args.as_coroutine().tupled_upvars_ty(), + Ty::new_coroutine_witness_for_coroutine(tcx, def_id, args), + ])) + } else { + Err(EvaluationFailure::NoSolution) + } } } - }, + } ty::UnsafeBinder(_) => Err(EvaluationFailure::NoSolution), diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 70a0896ae384c..ea903bac9d617 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -2877,7 +2877,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { obligations } - fn should_stall_coroutine(&self, def_id: DefId) -> bool { + pub(super) fn should_stall_coroutine(&self, def_id: DefId) -> bool { match self.infcx.typing_mode() { TypingMode::Analysis { defining_opaque_types_and_generators: stalled_generators } => { def_id.as_local().is_some_and(|def_id| stalled_generators.contains(&def_id)) diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index c7dc3fbc9a605..3e32a77885468 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -121,7 +121,7 @@ impl ty::Binder> { } pub fn to_host_effect_clause(self, cx: I, constness: BoundConstness) -> I::Clause { - self.map_bound(|trait_ref: TraitRef| { + self.map_bound(|trait_ref| { ty::ClauseKind::HostEffect(HostEffectPredicate { trait_ref, constness }) }) .upcast(cx)