about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_cranelift/example/mini_core.rs3
-rw-r--r--compiler/rustc_codegen_gcc/example/mini_core.rs3
-rw-r--r--compiler/rustc_hir/src/lang_items.rs1
-rw-r--r--compiler/rustc_hir_analysis/src/check/check.rs116
-rw-r--r--compiler/rustc_middle/src/traits/select.rs2
-rw-r--r--compiler/rustc_middle/src/ty/adt.rs4
-rw-r--r--compiler/rustc_middle/src/ty/context.rs1
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs8
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/effect_goals.rs7
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs7
-rw-r--r--compiler/rustc_next_trait_solver/src/solve/trait_goals.rs95
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs49
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/confirmation.rs92
-rw-r--r--compiler/rustc_trait_selection/src/traits/select/mod.rs3
-rw-r--r--compiler/rustc_type_ir/src/inherent.rs2
-rw-r--r--compiler/rustc_type_ir/src/lang_items.rs1
-rw-r--r--library/core/src/marker.rs17
-rw-r--r--tests/auxiliary/minicore.rs3
-rw-r--r--tests/ui/layout/malformed-unsized-type-in-union.rs1
-rw-r--r--tests/ui/layout/malformed-unsized-type-in-union.stderr17
21 files changed, 366 insertions, 67 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..beb7f1bf30e 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:
+    ///
+    /// ```
+    /// 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 bb59b4c40bd..e93a722ccc0 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -515,6 +515,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_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/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`.