diff options
| author | Jacob Pratt <jacob@jhpratt.dev> | 2025-02-13 03:53:30 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-13 03:53:30 -0500 |
| commit | 4ea261018a7ab5ab9328d29a0404347ec137bf0d (patch) | |
| tree | a23e66b2f3866145fd08350b53441ef0f589f126 | |
| parent | 4e6605fb0d05312a9e6f3083030737c73ecc3028 (diff) | |
| parent | d0564fda651a9b98539826e9aae05e1f8e7d73ab (diff) | |
| download | rust-4ea261018a7ab5ab9328d29a0404347ec137bf0d.tar.gz rust-4ea261018a7ab5ab9328d29a0404347ec137bf0d.zip | |
Rollup merge of #136660 - compiler-errors:BikeshedGuaranteedNoDrop, r=lcnr
Use a trait to enforce field validity for union fields + `unsafe` fields + `unsafe<>` binder types This PR introduces a new, internal-only trait called `BikeshedGuaranteedNoDrop`[^1] to faithfully model the field check that used to be implemented manually by `allowed_union_or_unsafe_field`. https://github.com/rust-lang/rust/blob/942db6782f4a28c55b0b75b38fd4394d0483390f/compiler/rustc_hir_analysis/src/check/check.rs#L84-L115 Copying over the doc comment from the trait: ```rust /// Marker trait for the types that are allowed in union fields, unsafe fields, /// and unsafe binder types. /// /// Implemented for: /// * `&T`, `&mut T` for all `T`, /// * `ManuallyDrop<T>` for all `T`, /// * tuples and arrays whose elements implement `BikeshedGuaranteedNoDrop`, /// * or otherwise, all types that are `Copy`. /// /// Notably, this doesn't include all trivially-destructible types for semver /// reasons. /// /// Bikeshed name for now. ``` As far as I am aware, there's no new behavior being guaranteed by this trait, since it operates the same as the manually implemented check. We could easily rip out this trait and go back to using the manually implemented check for union fields, however using a trait means that this code can be shared by WF for `unsafe<>` binders too. See the last commit. The only diagnostic changes are that this now fires false-negatives for fields that are ill-formed. I don't consider that to be much of a problem though. r? oli-obk [^1]: Please let's not bikeshed this name lol. There's no good name for `ValidForUnsafeFieldsUnsafeBindersAndUnionFields`.
27 files changed, 427 insertions, 167 deletions
diff --git a/compiler/rustc_codegen_cranelift/example/mini_core.rs b/compiler/rustc_codegen_cranelift/example/mini_core.rs index a0a381638c0..79820232496 100644 --- a/compiler/rustc_codegen_cranelift/example/mini_core.rs +++ b/compiler/rustc_codegen_cranelift/example/mini_core.rs @@ -57,6 +57,9 @@ impl<T: ?Sized> LegacyReceiver for Box<T> {} #[lang = "copy"] pub trait Copy {} +#[lang = "bikeshed_guaranteed_no_drop"] +pub trait BikeshedGuaranteedNoDrop {} + impl Copy for bool {} impl Copy for u8 {} impl Copy for u16 {} diff --git a/compiler/rustc_codegen_gcc/example/mini_core.rs b/compiler/rustc_codegen_gcc/example/mini_core.rs index 5a4ee0a198c..2ff1d757fd4 100644 --- a/compiler/rustc_codegen_gcc/example/mini_core.rs +++ b/compiler/rustc_codegen_gcc/example/mini_core.rs @@ -54,6 +54,9 @@ impl<T: ?Sized, A: Allocator> LegacyReceiver for Box<T, A> {} #[lang = "copy"] pub trait Copy {} +#[lang = "bikeshed_guaranteed_no_drop"] +pub trait BikeshedGuaranteedNoDrop {} + impl Copy for bool {} impl Copy for u8 {} impl Copy for u16 {} diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 1852987b167..34c0837b25a 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -351,6 +351,7 @@ language_item_table! { PhantomData, sym::phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1); ManuallyDrop, sym::manually_drop, manually_drop, Target::Struct, GenericRequirement::None; + BikeshedGuaranteedNoDrop, sym::bikeshed_guaranteed_no_drop, bikeshed_guaranteed_no_drop, Target::Trait, GenericRequirement::Exact(0); MaybeUninit, sym::maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 9ed56d7bde5..71a10ad3a0c 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -6,7 +6,7 @@ use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::MultiSpan; use rustc_errors::codes::*; use rustc_hir::def::{CtorKind, DefKind}; -use rustc_hir::{Node, intravisit}; +use rustc_hir::{LangItem, Node, intravisit}; use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt}; use rustc_infer::traits::{Obligation, ObligationCauseCode}; use rustc_lint_defs::builtin::{ @@ -27,6 +27,7 @@ use rustc_session::lint::builtin::UNINHABITED_STATIC; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::error_reporting::traits::on_unimplemented::OnUnimplementedDirective; use rustc_trait_selection::traits; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_type_ir::fold::TypeFoldable; use tracing::{debug, instrument}; use ty::TypingMode; @@ -87,89 +88,76 @@ fn allowed_union_or_unsafe_field<'tcx>( typing_env: ty::TypingEnv<'tcx>, span: Span, ) -> bool { - // We don't just accept all !needs_drop fields, due to semver concerns. - let allowed = match ty.kind() { - ty::Ref(..) => true, // references never drop (even mutable refs, which are non-Copy and hence fail the later check) - ty::Tuple(tys) => { - // allow tuples of allowed types - tys.iter().all(|ty| allowed_union_or_unsafe_field(tcx, ty, typing_env, span)) - } - ty::Array(elem, _len) => { - // Like `Copy`, we do *not* special-case length 0. - allowed_union_or_unsafe_field(tcx, *elem, typing_env, span) - } - _ => { - // Fallback case: allow `ManuallyDrop` and things that are `Copy`, - // also no need to report an error if the type is unresolved. - ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop()) - || tcx.type_is_copy_modulo_regions(typing_env, ty) - || ty.references_error() - } - }; - if allowed && ty.needs_drop(tcx, typing_env) { - // This should never happen. But we can get here e.g. in case of name resolution errors. - tcx.dcx() - .span_delayed_bug(span, "we should never accept maybe-dropping union or unsafe fields"); + // HACK (not that bad of a hack don't worry): Some codegen tests don't even define proper + // impls for `Copy`. Let's short-circuit here for this validity check, since a lot of them + // use unions. We should eventually fix all the tests to define that lang item or use + // minicore stubs. + if ty.is_trivially_pure_clone_copy() { + return true; } - allowed + // If `BikeshedGuaranteedNoDrop` is not defined in a `#[no_core]` test, fall back to `Copy`. + // This is an underapproximation of `BikeshedGuaranteedNoDrop`, + let def_id = tcx + .lang_items() + .get(LangItem::BikeshedGuaranteedNoDrop) + .unwrap_or_else(|| tcx.require_lang_item(LangItem::Copy, Some(span))); + let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, ty) else { + tcx.dcx().span_delayed_bug(span, "could not normalize field type"); + return true; + }; + let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env); + infcx.predicate_must_hold_modulo_regions(&Obligation::new( + tcx, + ObligationCause::dummy_with_span(span), + param_env, + ty::TraitRef::new(tcx, def_id, [ty]), + )) } /// Check that the fields of the `union` do not need dropping. fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> bool { - let item_type = tcx.type_of(item_def_id).instantiate_identity(); - if let ty::Adt(def, args) = item_type.kind() { - assert!(def.is_union()); - - let typing_env = ty::TypingEnv::non_body_analysis(tcx, item_def_id); - for field in &def.non_enum_variant().fields { - let Ok(field_ty) = tcx.try_normalize_erasing_regions(typing_env, field.ty(tcx, args)) - else { - tcx.dcx().span_delayed_bug(span, "could not normalize field type"); - continue; - }; + let def = tcx.adt_def(item_def_id); + assert!(def.is_union()); - if !allowed_union_or_unsafe_field(tcx, field_ty, typing_env, span) { - let (field_span, ty_span) = match tcx.hir().get_if_local(field.did) { - // We are currently checking the type this field came from, so it must be local. - Some(Node::Field(field)) => (field.span, field.ty.span), - _ => unreachable!("mir field has to correspond to hir field"), - }; - tcx.dcx().emit_err(errors::InvalidUnionField { - field_span, - sugg: errors::InvalidUnionFieldSuggestion { - lo: ty_span.shrink_to_lo(), - hi: ty_span.shrink_to_hi(), - }, - note: (), - }); - return false; - } + let typing_env = ty::TypingEnv::non_body_analysis(tcx, item_def_id); + let args = ty::GenericArgs::identity_for_item(tcx, item_def_id); + + for field in &def.non_enum_variant().fields { + if !allowed_union_or_unsafe_field(tcx, field.ty(tcx, args), typing_env, span) { + let (field_span, ty_span) = match tcx.hir().get_if_local(field.did) { + // We are currently checking the type this field came from, so it must be local. + Some(Node::Field(field)) => (field.span, field.ty.span), + _ => unreachable!("mir field has to correspond to hir field"), + }; + tcx.dcx().emit_err(errors::InvalidUnionField { + field_span, + sugg: errors::InvalidUnionFieldSuggestion { + lo: ty_span.shrink_to_lo(), + hi: ty_span.shrink_to_hi(), + }, + note: (), + }); + return false; } - } else { - span_bug!(span, "unions must be ty::Adt, but got {:?}", item_type.kind()); } + true } /// Check that the unsafe fields do not need dropping. fn check_unsafe_fields(tcx: TyCtxt<'_>, item_def_id: LocalDefId) { let span = tcx.def_span(item_def_id); - let item_type = tcx.type_of(item_def_id).instantiate_identity(); - let ty::Adt(def, args) = item_type.kind() else { - span_bug!(span, "structs/enums must be ty::Adt, but got {:?}", item_type.kind()); - }; + let def = tcx.adt_def(item_def_id); + let typing_env = ty::TypingEnv::non_body_analysis(tcx, item_def_id); + let args = ty::GenericArgs::identity_for_item(tcx, item_def_id); + for field in def.all_fields() { if !field.safety.is_unsafe() { continue; } - let Ok(field_ty) = tcx.try_normalize_erasing_regions(typing_env, field.ty(tcx, args)) - else { - tcx.dcx().span_delayed_bug(span, "could not normalize field type"); - continue; - }; - if !allowed_union_or_unsafe_field(tcx, field_ty, typing_env, span) { + if !allowed_union_or_unsafe_field(tcx, field.ty(tcx, args), typing_env, span) { let hir::Node::Field(field) = tcx.hir_node_by_def_id(field.did.expect_local()) else { unreachable!("field has to correspond to hir field") }; diff --git a/compiler/rustc_middle/src/traits/select.rs b/compiler/rustc_middle/src/traits/select.rs index b7cd545d02d..811bd8fb458 100644 --- a/compiler/rustc_middle/src/traits/select.rs +++ b/compiler/rustc_middle/src/traits/select.rs @@ -168,6 +168,8 @@ pub enum SelectionCandidate<'tcx> { BuiltinObjectCandidate, BuiltinUnsizeCandidate, + + BikeshedGuaranteedNoDropCandidate, } /// The result of trait evaluation. The order is important diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index e28fcc555dc..b529d17540a 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -217,6 +217,10 @@ impl<'tcx> rustc_type_ir::inherent::AdtDef<TyCtxt<'tcx>> for AdtDef<'tcx> { self.is_phantom_data() } + fn is_manually_drop(self) -> bool { + self.is_manually_drop() + } + fn all_field_tys( self, tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index a9b4593c13d..9c07981a027 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -690,6 +690,7 @@ bidirectional_lang_item_map! { AsyncFnOnce, AsyncFnOnceOutput, AsyncIterator, + BikeshedGuaranteedNoDrop, CallOnceFuture, CallRefFuture, Clone, diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index d0b01b14d63..a3274fb4011 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -267,6 +267,11 @@ where goal: Goal<I, Self>, ) -> Result<Candidate<I>, NoSolution>; + fn consider_builtin_bikeshed_guaranteed_no_drop_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal<I, Self>, + ) -> Result<Candidate<I>, NoSolution>; + /// Consider (possibly several) candidates to upcast or unsize a type to another /// type, excluding the coercion of a sized type into a `dyn Trait`. /// @@ -478,6 +483,9 @@ where Some(TraitSolverLangItem::TransmuteTrait) => { G::consider_builtin_transmute_candidate(self, goal) } + Some(TraitSolverLangItem::BikeshedGuaranteedNoDrop) => { + G::consider_builtin_bikeshed_guaranteed_no_drop_candidate(self, goal) + } _ => Err(NoSolution), } }; 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 fb53e778e60..eb398e8724d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -374,6 +374,13 @@ where unreachable!("TransmuteFrom is not const") } + fn consider_builtin_bikeshed_guaranteed_no_drop_candidate( + _ecx: &mut EvalCtxt<'_, D>, + _goal: Goal<I, Self>, + ) -> Result<Candidate<I>, NoSolution> { + unreachable!("BikeshedGuaranteedNoDrop is not const"); + } + fn consider_structural_builtin_unsize_candidates( _ecx: &mut EvalCtxt<'_, D>, _goal: Goal<I, Self>, diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index c8ea0b119ff..1d0ec7b45ca 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -942,6 +942,13 @@ where ) -> Result<Candidate<I>, NoSolution> { panic!("`TransmuteFrom` does not have an associated type: {:?}", goal) } + + fn consider_builtin_bikeshed_guaranteed_no_drop_candidate( + _ecx: &mut EvalCtxt<'_, D>, + goal: Goal<I, Self>, + ) -> Result<Candidate<I>, NoSolution> { + unreachable!("`BikeshedGuaranteedNoDrop` does not have an associated type: {:?}", goal) + } } impl<D, I> EvalCtxt<'_, D> diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 46fae5c6fa4..dabfa5cc04c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -625,6 +625,101 @@ where }) } + /// NOTE: This is implemented as a built-in goal and not a set of impls like: + /// + /// ```rust,ignore (illustrative) + /// impl<T> BikeshedGuaranteedNoDrop for T where T: Copy {} + /// impl<T> BikeshedGuaranteedNoDrop for ManuallyDrop<T> {} + /// ``` + /// + /// because these impls overlap, and I'd rather not build a coherence hack for + /// this harmless overlap. + fn consider_builtin_bikeshed_guaranteed_no_drop_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal<I, Self>, + ) -> Result<Candidate<I>, NoSolution> { + if goal.predicate.polarity != ty::PredicatePolarity::Positive { + return Err(NoSolution); + } + + let cx = ecx.cx(); + ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + let ty = goal.predicate.self_ty(); + match ty.kind() { + // `&mut T` and `&T` always implement `BikeshedGuaranteedNoDrop`. + ty::Ref(..) => {} + // `ManuallyDrop<T>` always implements `BikeshedGuaranteedNoDrop`. + ty::Adt(def, _) if def.is_manually_drop() => {} + // Arrays and tuples implement `BikeshedGuaranteedNoDrop` only if + // their constituent types implement `BikeshedGuaranteedNoDrop`. + ty::Tuple(tys) => { + ecx.add_goals( + GoalSource::ImplWhereBound, + tys.iter().map(|elem_ty| { + goal.with(cx, ty::TraitRef::new(cx, goal.predicate.def_id(), [elem_ty])) + }), + ); + } + ty::Array(elem_ty, _) => { + ecx.add_goal( + GoalSource::ImplWhereBound, + goal.with(cx, ty::TraitRef::new(cx, goal.predicate.def_id(), [elem_ty])), + ); + } + + // All other types implement `BikeshedGuaranteedNoDrop` only if + // they implement `Copy`. We could be smart here and short-circuit + // some trivially `Copy`/`!Copy` types, but there's no benefit. + ty::FnDef(..) + | ty::FnPtr(..) + | ty::Error(_) + | ty::Uint(_) + | ty::Int(_) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) + | ty::Bool + | ty::Float(_) + | ty::Char + | ty::RawPtr(..) + | ty::Never + | ty::Pat(..) + | ty::Dynamic(..) + | ty::Str + | ty::Slice(_) + | ty::Foreign(..) + | ty::Adt(..) + | ty::Alias(..) + | ty::Param(_) + | ty::Placeholder(..) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::UnsafeBinder(_) + | ty::CoroutineWitness(..) => { + ecx.add_goal( + GoalSource::ImplWhereBound, + goal.with( + cx, + ty::TraitRef::new( + cx, + cx.require_lang_item(TraitSolverLangItem::Copy), + [ty], + ), + ), + ); + } + + ty::Bound(..) + | ty::Infer( + ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_), + ) => { + panic!("unexpected type `{ty:?}`") + } + } + + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + } + /// ```ignore (builtin impl example) /// trait Trait { /// fn foo(&self); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 9734926810a..11dc2edf986 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -516,6 +516,7 @@ symbols! { bang, begin_panic, bench, + bikeshed_guaranteed_no_drop, bin, binaryheap_iter, bind_by_move_pattern_guards, diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index bfab009a7e3..5b362b2356e 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -102,6 +102,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { self.assemble_candidate_for_tuple(obligation, &mut candidates); } else if tcx.is_lang_item(def_id, LangItem::FnPtrTrait) { self.assemble_candidates_for_fn_ptr_trait(obligation, &mut candidates); + } else if tcx.is_lang_item(def_id, LangItem::BikeshedGuaranteedNoDrop) { + self.assemble_candidates_for_bikeshed_guaranteed_no_drop_trait( + obligation, + &mut candidates, + ); } else { if tcx.is_lang_item(def_id, LangItem::Clone) { // Same builtin conditions as `Copy`, i.e., every type which has builtin support @@ -1184,4 +1189,48 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } } + + fn assemble_candidates_for_bikeshed_guaranteed_no_drop_trait( + &mut self, + obligation: &PolyTraitObligation<'tcx>, + candidates: &mut SelectionCandidateSet<'tcx>, + ) { + match obligation.predicate.self_ty().skip_binder().kind() { + ty::Ref(..) + | ty::Adt(..) + | ty::Tuple(_) + | ty::Array(..) + | ty::FnDef(..) + | ty::FnPtr(..) + | ty::Error(_) + | ty::Uint(_) + | ty::Int(_) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) + | ty::Bool + | ty::Float(_) + | ty::Char + | ty::RawPtr(..) + | ty::Never + | ty::Pat(..) + | ty::Dynamic(..) + | ty::Str + | ty::Slice(_) + | ty::Foreign(..) + | ty::Alias(..) + | ty::Param(_) + | ty::Placeholder(..) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::UnsafeBinder(_) + | ty::CoroutineWitness(..) + | ty::Bound(..) => { + candidates.vec.push(BikeshedGuaranteedNoDropCandidate); + } + + ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + candidates.ambiguous = true; + } + } + } } diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index eb4adde716a..32cbb97e314 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -20,6 +20,7 @@ use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, Upcast}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::DefId; use rustc_type_ir::elaborate; +use thin_vec::thin_vec; use tracing::{debug, instrument}; use super::SelectionCandidate::{self, *}; @@ -130,6 +131,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { TraitUpcastingUnsizeCandidate(idx) => { self.confirm_trait_upcasting_unsize_candidate(obligation, idx)? } + + BikeshedGuaranteedNoDropCandidate => { + self.confirm_bikeshed_guaranteed_no_drop_candidate(obligation) + } }; // The obligations returned by confirmation are recursively evaluated @@ -1346,6 +1351,93 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { _ => bug!("source: {source}, target: {target}"), }) } + + fn confirm_bikeshed_guaranteed_no_drop_candidate( + &mut self, + obligation: &PolyTraitObligation<'tcx>, + ) -> ImplSource<'tcx, PredicateObligation<'tcx>> { + let mut obligations = thin_vec![]; + + let tcx = self.tcx(); + let self_ty = obligation.predicate.self_ty(); + match *self_ty.skip_binder().kind() { + // `&mut T` and `&T` always implement `BikeshedGuaranteedNoDrop`. + ty::Ref(..) => {} + // `ManuallyDrop<T>` always implements `BikeshedGuaranteedNoDrop`. + ty::Adt(def, _) if def.is_manually_drop() => {} + // Arrays and tuples implement `BikeshedGuaranteedNoDrop` only if + // their constituent types implement `BikeshedGuaranteedNoDrop`. + ty::Tuple(tys) => { + obligations.extend(tys.iter().map(|elem_ty| { + obligation.with( + tcx, + self_ty.rebind(ty::TraitRef::new( + tcx, + obligation.predicate.def_id(), + [elem_ty], + )), + ) + })); + } + ty::Array(elem_ty, _) => { + obligations.push(obligation.with( + tcx, + self_ty.rebind(ty::TraitRef::new( + tcx, + obligation.predicate.def_id(), + [elem_ty], + )), + )); + } + + // All other types implement `BikeshedGuaranteedNoDrop` only if + // they implement `Copy`. We could be smart here and short-circuit + // some trivially `Copy`/`!Copy` types, but there's no benefit. + ty::FnDef(..) + | ty::FnPtr(..) + | ty::Error(_) + | ty::Uint(_) + | ty::Int(_) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) + | ty::Bool + | ty::Float(_) + | ty::Char + | ty::RawPtr(..) + | ty::Never + | ty::Pat(..) + | ty::Dynamic(..) + | ty::Str + | ty::Slice(_) + | ty::Foreign(..) + | ty::Adt(..) + | ty::Alias(..) + | ty::Param(_) + | ty::Placeholder(..) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::UnsafeBinder(_) + | ty::CoroutineWitness(..) + | ty::Bound(..) => { + obligations.push(obligation.with( + tcx, + self_ty.map_bound(|ty| { + ty::TraitRef::new( + tcx, + tcx.require_lang_item(LangItem::Copy, Some(obligation.cause.span)), + [ty], + ) + }), + )); + } + + ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + panic!("unexpected type `{self_ty:?}`") + } + } + + ImplSource::Builtin(BuiltinImplSource::Misc, obligations) + } } /// Compute a goal that some RPITIT (right now, only RPITITs corresponding to Futures) diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 6b1e1774f03..436ce3dddd9 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -1949,7 +1949,8 @@ impl<'tcx> SelectionContext<'_, 'tcx> { | TraitAliasCandidate | TraitUpcastingUnsizeCandidate(_) | BuiltinObjectCandidate - | BuiltinUnsizeCandidate => false, + | BuiltinUnsizeCandidate + | BikeshedGuaranteedNoDropCandidate => false, // Non-global param candidates have already been handled, global // where-bounds get ignored. ParamCandidate(_) | ImplCandidate(_) => true, diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index c27bd269b0d..2f72b44f6b6 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -881,7 +881,10 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> { ty.map_bound(|ty| { ty::TraitRef::new( self.tcx(), - self.tcx().require_lang_item(LangItem::Copy, Some(self.span)), + self.tcx().require_lang_item( + LangItem::BikeshedGuaranteedNoDrop, + Some(self.span), + ), [ty], ) }), diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 4e6d645e6fa..6924216bd26 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -540,6 +540,8 @@ pub trait AdtDef<I: Interner>: Copy + Debug + Hash + Eq { fn is_phantom_data(self) -> bool; + fn is_manually_drop(self) -> bool; + // FIXME: perhaps use `all_fields` and expose `FieldDef`. fn all_field_tys(self, interner: I) -> ty::EarlyBinder<I, impl IntoIterator<Item = I::Ty>>; diff --git a/compiler/rustc_type_ir/src/lang_items.rs b/compiler/rustc_type_ir/src/lang_items.rs index eeb80bc3ab4..65f7cdf8f92 100644 --- a/compiler/rustc_type_ir/src/lang_items.rs +++ b/compiler/rustc_type_ir/src/lang_items.rs @@ -10,6 +10,7 @@ pub enum TraitSolverLangItem { AsyncFnOnce, AsyncFnOnceOutput, AsyncIterator, + BikeshedGuaranteedNoDrop, CallOnceFuture, CallRefFuture, Clone, diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 029c8b356d0..042ee419d57 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -453,6 +453,23 @@ impl Copy for ! {} #[stable(feature = "rust1", since = "1.0.0")] impl<T: ?Sized> Copy for &T {} +/// Marker trait for the types that are allowed in union fields, unsafe fields, +/// and unsafe binder types. +/// +/// Implemented for: +/// * `&T`, `&mut T` for all `T`, +/// * `ManuallyDrop<T>` for all `T`, +/// * tuples and arrays whose elements implement `BikeshedGuaranteedNoDrop`, +/// * or otherwise, all types that are `Copy`. +/// +/// Notably, this doesn't include all trivially-destructible types for semver +/// reasons. +/// +/// Bikeshed name for now. +#[unstable(feature = "bikeshed_guaranteed_no_drop", issue = "none")] +#[cfg_attr(not(bootstrap), lang = "bikeshed_guaranteed_no_drop")] +pub trait BikeshedGuaranteedNoDrop {} + /// Types for which it is safe to share references between threads. /// /// This trait is automatically implemented when the compiler determines diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index a68552175c3..1c5f9eeba3c 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -39,6 +39,9 @@ impl<T: ?Sized> LegacyReceiver for &mut T {} #[lang = "copy"] pub trait Copy: Sized {} +#[lang = "bikeshed_guaranteed_no_drop"] +pub trait BikeshedGuaranteedNoDrop {} + impl_marker_trait!( Copy => [ bool, char, diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs index e6af1d60e77..b052aabb499 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs @@ -1,12 +1,12 @@ //@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib //@ needs-llvm-components: arm +//@ add-core-stubs + #![feature(abi_c_cmse_nonsecure_call, no_core, lang_items)] #![no_core] -#[lang = "sized"] -pub trait Sized {} -#[lang = "copy"] -pub trait Copy {} -impl Copy for u32 {} + +extern crate minicore; +use minicore::*; #[repr(C)] pub struct ReprCU64(u64); diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs index 5746d14f9b1..23d55526e57 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.rs @@ -1,13 +1,12 @@ //@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib //@ needs-llvm-components: arm +//@ add-core-stubs + #![feature(cmse_nonsecure_entry, no_core, lang_items)] #![no_core] -#[lang = "sized"] -pub trait Sized {} -#[lang = "copy"] -pub trait Copy {} -impl Copy for u32 {} -impl Copy for u8 {} + +extern crate minicore; +use minicore::*; #[repr(C)] pub struct ReprCU64(u64); diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr index 9c885d95318..d37d9b5e8ff 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/return-via-stack.stderr @@ -1,5 +1,5 @@ error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:25:48 + --> $DIR/return-via-stack.rs:24:48 | LL | pub extern "C-cmse-nonsecure-entry" fn f1() -> ReprCU64 { | ^^^^^^^^ this type doesn't fit in the available registers @@ -8,7 +8,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn f1() -> ReprCU64 { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:30:48 + --> $DIR/return-via-stack.rs:29:48 | LL | pub extern "C-cmse-nonsecure-entry" fn f2() -> ReprCBytes { | ^^^^^^^^^^ this type doesn't fit in the available registers @@ -17,7 +17,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn f2() -> ReprCBytes { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:35:48 + --> $DIR/return-via-stack.rs:34:48 | LL | pub extern "C-cmse-nonsecure-entry" fn f3() -> U64Compound { | ^^^^^^^^^^^ this type doesn't fit in the available registers @@ -26,7 +26,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn f3() -> U64Compound { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:40:48 + --> $DIR/return-via-stack.rs:39:48 | LL | pub extern "C-cmse-nonsecure-entry" fn f4() -> ReprCAlign16 { | ^^^^^^^^^^^^ this type doesn't fit in the available registers @@ -35,7 +35,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn f4() -> ReprCAlign16 { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:47:48 + --> $DIR/return-via-stack.rs:46:48 | LL | pub extern "C-cmse-nonsecure-entry" fn f5() -> [u8; 5] { | ^^^^^^^ this type doesn't fit in the available registers @@ -44,7 +44,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn f5() -> [u8; 5] { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:53:50 + --> $DIR/return-via-stack.rs:52:50 | LL | pub extern "C-cmse-nonsecure-entry" fn u128() -> u128 { | ^^^^ this type doesn't fit in the available registers @@ -53,7 +53,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn u128() -> u128 { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:59:50 + --> $DIR/return-via-stack.rs:58:50 | LL | pub extern "C-cmse-nonsecure-entry" fn i128() -> i128 { | ^^^^ this type doesn't fit in the available registers @@ -62,7 +62,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn i128() -> i128 { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:76:56 + --> $DIR/return-via-stack.rs:75:56 | LL | pub extern "C-cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { | ^^^^^^^^^^^^^^^^ this type doesn't fit in the available registers @@ -71,7 +71,7 @@ LL | pub extern "C-cmse-nonsecure-entry" fn union_rust() -> ReprRustUnionU64 { = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size error[E0798]: return value of `"C-cmse-nonsecure-entry"` function too large to pass via registers - --> $DIR/return-via-stack.rs:81:53 + --> $DIR/return-via-stack.rs:80:53 | LL | pub extern "C-cmse-nonsecure-entry" fn union_c() -> ReprCUnionU64 { | ^^^^^^^^^^^^^ this type doesn't fit in the available registers diff --git a/tests/ui/layout/malformed-unsized-type-in-union.rs b/tests/ui/layout/malformed-unsized-type-in-union.rs index 5d8ec576cf0..e97024ce9d7 100644 --- a/tests/ui/layout/malformed-unsized-type-in-union.rs +++ b/tests/ui/layout/malformed-unsized-type-in-union.rs @@ -2,6 +2,7 @@ union W { s: dyn Iterator<Item = Missing> } //~^ ERROR cannot find type `Missing` in this scope +//~| ERROR field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be used in a union static ONCE: W = todo!(); diff --git a/tests/ui/layout/malformed-unsized-type-in-union.stderr b/tests/ui/layout/malformed-unsized-type-in-union.stderr index ad4f0cda19e..bdfabc0b151 100644 --- a/tests/ui/layout/malformed-unsized-type-in-union.stderr +++ b/tests/ui/layout/malformed-unsized-type-in-union.stderr @@ -4,6 +4,19 @@ error[E0412]: cannot find type `Missing` in this scope LL | union W { s: dyn Iterator<Item = Missing> } | ^^^^^^^ not found in this scope -error: aborting due to 1 previous error +error[E0740]: field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be used in a union + --> $DIR/malformed-unsized-type-in-union.rs:3:11 + | +LL | union W { s: dyn Iterator<Item = Missing> } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: union fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` +help: wrap the field type in `ManuallyDrop<...>` + | +LL | union W { s: std::mem::ManuallyDrop<dyn Iterator<Item = Missing>> } + | +++++++++++++++++++++++ + + +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0412`. +Some errors have detailed explanations: E0412, E0740. +For more information about an error, try `rustc --explain E0412`. diff --git a/tests/ui/unsafe-binders/moves.rs b/tests/ui/unsafe-binders/moves.rs index 5bfcee62402..9397c2bc20f 100644 --- a/tests/ui/unsafe-binders/moves.rs +++ b/tests/ui/unsafe-binders/moves.rs @@ -1,40 +1,40 @@ -//@ known-bug: unknown - #![feature(unsafe_binders)] -// FIXME(unsafe_binders) ~^ WARN the feature `unsafe_binders` is incomplete +//~^ WARN the feature `unsafe_binders` is incomplete -use std::unsafe_binder::{wrap_binder, unwrap_binder}; -use std::mem::{drop, ManuallyDrop}; +use std::mem::{ManuallyDrop, drop}; +use std::unsafe_binder::{unwrap_binder, wrap_binder}; +#[derive(Default)] struct NotCopyInner; type NotCopy = ManuallyDrop<NotCopyInner>; fn use_after_wrap() { unsafe { - let base = NotCopy; + let base = NotCopy::default(); let binder: unsafe<> NotCopy = wrap_binder!(base); drop(base); - // FIXME(unsafe_binders) ~^ ERROR use of moved value: `base` + //~^ ERROR use of moved value: `base` } } fn move_out_of_wrap() { unsafe { - let binder: unsafe<> NotCopy = wrap_binder!(NotCopy); + let binder: unsafe<> NotCopy = wrap_binder!(NotCopy::default()); drop(unwrap_binder!(binder)); drop(unwrap_binder!(binder)); - // FIXME(unsafe_binders) ~^ ERROR use of moved value: `binder` + //~^ ERROR use of moved value: `binder` } } fn not_conflicting() { unsafe { - let binder: unsafe<> (NotCopy, NotCopy) = wrap_binder!((NotCopy, NotCopy)); + let binder: unsafe<> (NotCopy, NotCopy) = + wrap_binder!((NotCopy::default(), NotCopy::default())); drop(unwrap_binder!(binder).0); drop(unwrap_binder!(binder).1); - // ^ NOT a problem. + // ^ NOT a problem, since the moves are disjoint. drop(unwrap_binder!(binder).0); - // FIXME(unsafe_binders) ~^ ERROR use of moved value: `binder.0` + //~^ ERROR use of moved value: `binder.0` } } diff --git a/tests/ui/unsafe-binders/moves.stderr b/tests/ui/unsafe-binders/moves.stderr index ca507964008..0f976d9e845 100644 --- a/tests/ui/unsafe-binders/moves.stderr +++ b/tests/ui/unsafe-binders/moves.stderr @@ -1,37 +1,5 @@ -error[E0423]: expected value, found type alias `NotCopy` - --> $DIR/moves.rs:14:20 - | -LL | let base = NotCopy; - | ^^^^^^^ - | - = note: can't use a type alias as a constructor - -error[E0423]: expected value, found type alias `NotCopy` - --> $DIR/moves.rs:23:53 - | -LL | let binder: unsafe<> NotCopy = wrap_binder!(NotCopy); - | ^^^^^^^ - | - = note: can't use a type alias as a constructor - -error[E0423]: expected value, found type alias `NotCopy` - --> $DIR/moves.rs:32:65 - | -LL | let binder: unsafe<> (NotCopy, NotCopy) = wrap_binder!((NotCopy, NotCopy)); - | ^^^^^^^ - | - = note: can't use a type alias as a constructor - -error[E0423]: expected value, found type alias `NotCopy` - --> $DIR/moves.rs:32:74 - | -LL | let binder: unsafe<> (NotCopy, NotCopy) = wrap_binder!((NotCopy, NotCopy)); - | ^^^^^^^ - | - = note: can't use a type alias as a constructor - warning: the feature `unsafe_binders` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/moves.rs:3:12 + --> $DIR/moves.rs:1:12 | LL | #![feature(unsafe_binders)] | ^^^^^^^^^^^^^^ @@ -39,47 +7,38 @@ LL | #![feature(unsafe_binders)] = note: see issue #130516 <https://github.com/rust-lang/rust/issues/130516> for more information = note: `#[warn(incomplete_features)]` on by default -error[E0277]: the trait bound `NotCopyInner: Copy` is not satisfied - --> $DIR/moves.rs:15:21 +error[E0382]: use of moved value: `base` + --> $DIR/moves.rs:15:14 | +LL | let base = NotCopy::default(); + | ---- move occurs because `base` has type `ManuallyDrop<NotCopyInner>`, which does not implement the `Copy` trait LL | let binder: unsafe<> NotCopy = wrap_binder!(base); - | ^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `NotCopyInner` - | - = note: required for `ManuallyDrop<NotCopyInner>` to implement `Copy` -help: consider annotating `NotCopyInner` with `#[derive(Copy)]` - | -LL + #[derive(Copy)] -LL | struct NotCopyInner; - | + | ---- value moved here +LL | drop(base); + | ^^^^ value used here after move -error[E0277]: the trait bound `NotCopyInner: Copy` is not satisfied - --> $DIR/moves.rs:23:21 +error[E0382]: use of moved value: `binder` + --> $DIR/moves.rs:24:14 | -LL | let binder: unsafe<> NotCopy = wrap_binder!(NotCopy); - | ^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `NotCopyInner` - | - = note: required for `ManuallyDrop<NotCopyInner>` to implement `Copy` -help: consider annotating `NotCopyInner` with `#[derive(Copy)]` - | -LL + #[derive(Copy)] -LL | struct NotCopyInner; +LL | drop(unwrap_binder!(binder)); + | ---------------------- value moved here +LL | drop(unwrap_binder!(binder)); + | ^^^^^^^^^^^^^^^^^^^^^^ value used here after move | + = note: move occurs because `binder` has type `ManuallyDrop<NotCopyInner>`, which does not implement the `Copy` trait + = note: this error originates in the macro `unwrap_binder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `NotCopyInner: Copy` is not satisfied - --> $DIR/moves.rs:32:21 - | -LL | let binder: unsafe<> (NotCopy, NotCopy) = wrap_binder!((NotCopy, NotCopy)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `NotCopyInner` - | - = note: required for `ManuallyDrop<NotCopyInner>` to implement `Copy` - = note: required because it appears within the type `(ManuallyDrop<NotCopyInner>, ManuallyDrop<NotCopyInner>)` -help: consider annotating `NotCopyInner` with `#[derive(Copy)]` +error[E0382]: use of moved value: `binder.0` + --> $DIR/moves.rs:36:14 | -LL + #[derive(Copy)] -LL | struct NotCopyInner; +LL | drop(unwrap_binder!(binder).0); + | ------------------------ value moved here +... +LL | drop(unwrap_binder!(binder).0); + | ^^^^^^^^^^^^^^^^^^^^^^^^ value used here after move | + = note: move occurs because `binder.0` has type `ManuallyDrop<NotCopyInner>`, which does not implement the `Copy` trait -error: aborting due to 7 previous errors; 1 warning emitted +error: aborting due to 3 previous errors; 1 warning emitted -Some errors have detailed explanations: E0277, E0423. -For more information about an error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0382`. |
