From e4996785fd43375e54e5b3cfe1aa55cffe2d90d9 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Fri, 5 Jun 2026 19:22:43 +0000 Subject: [PATCH] transmutability: model NonZero scalar ranges Teach rustc_transmute to model scalar validity ranges for transparent std NonZero wrappers, including integer NonZero types and NonZero. Also handle scalar-backed ADT layouts whose fields are represented with FieldsShape::Primitive. Thread the proving body through transmutability obligations so the trait solver can evaluate destination ADT, variant, and field safety invariants relative to the caller module. This keeps private fields and unsafe fields caller-sensitive in both old and next solver paths. Add UI coverage for std NonZero integers, NonZero, option/array/ tuple compositions, same-width signed/unsigned conversions, reference safety invariants, and field visibility barriers. --- .../src/type_check/canonical.rs | 5 +- compiler/rustc_middle/src/traits/query.rs | 2 + .../rustc_next_trait_solver/src/delegate.rs | 1 + .../src/solve/eval_ctxt/mod.rs | 32 +- .../src/solve/eval_ctxt/probe.rs | 1 + .../src/solve/inspect/build.rs | 11 +- .../src/solve/search_graph.rs | 40 +- .../traits/fulfillment_errors.rs | 6 +- .../src/solve/delegate.rs | 11 +- .../src/solve/fulfill.rs | 8 +- .../src/solve/fulfill/derive_errors.rs | 1 + .../src/solve/normalize.rs | 3 +- .../traits/query/type_op/prove_predicate.rs | 2 +- .../src/traits/select/confirmation.rs | 4 +- compiler/rustc_traits/src/type_op.rs | 5 +- compiler/rustc_transmute/src/layout/mod.rs | 16 +- compiler/rustc_transmute/src/layout/tree.rs | 310 +++++++++- compiler/rustc_transmute/src/lib.rs | 10 +- .../src/maybe_transmutable/mod.rs | 17 +- .../src/maybe_transmutable/query_context.rs | 15 +- .../src/maybe_transmutable/tests.rs | 50 ++ library/core/src/mem/transmutability.rs | 28 +- tests/ui/transmutability/nonzero-pass.rs | 137 +++++ tests/ui/transmutability/nonzero.rs | 73 +++ tests/ui/transmutability/nonzero.stderr | 558 ++++++++++++++++++ .../safety/field-visibility.rs | 49 ++ .../safety/field-visibility.stderr | 33 ++ ..._reject_if_ref_src_has_safety_invariant.rs | 4 +- 28 files changed, 1332 insertions(+), 100 deletions(-) create mode 100644 tests/ui/transmutability/nonzero-pass.rs create mode 100644 tests/ui/transmutability/nonzero.rs create mode 100644 tests/ui/transmutability/nonzero.stderr create mode 100644 tests/ui/transmutability/safety/field-visibility.rs create mode 100644 tests/ui/transmutability/safety/field-visibility.stderr diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index 56c800dcf7c7e..bb0bc99b030d6 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -179,7 +179,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let _: Result<_, ErrorGuaranteed> = self.fully_perform_op( locations, category, - param_env.and(type_op::prove_predicate::ProvePredicate { predicate }), + param_env.and(type_op::prove_predicate::ProvePredicate { + predicate, + body_id: self.infcx.root_def_id, + }), ); } diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs index d6e6c56c00312..29afc4ce12002 100644 --- a/compiler/rustc_middle/src/traits/query.rs +++ b/compiler/rustc_middle/src/traits/query.rs @@ -16,6 +16,7 @@ use crate::ty::{self, GenericArg, Ty, TyCtxt}; pub mod type_op { use rustc_macros::{StableHash, TypeFoldable, TypeVisitable}; + use rustc_span::def_id::LocalDefId; use crate::ty::{Predicate, Ty, UserType}; @@ -40,6 +41,7 @@ pub mod type_op { #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, StableHash, TypeFoldable, TypeVisitable)] pub struct ProvePredicate<'tcx> { pub predicate: Predicate<'tcx>, + pub body_id: LocalDefId, } /// Normalizes, but not in the new solver. diff --git a/compiler/rustc_next_trait_solver/src/delegate.rs b/compiler/rustc_next_trait_solver/src/delegate.rs index 8fd7d6d0471c7..d1a1f500749c4 100644 --- a/compiler/rustc_next_trait_solver/src/delegate.rs +++ b/compiler/rustc_next_trait_solver/src/delegate.rs @@ -88,5 +88,6 @@ pub trait SolverDelegate: Deref + Sized { src: ::Ty, dst: ::Ty, assume: ::Const, + body_id: Option<::LocalDefId>, ) -> Result; } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 23fcfc2788656..eb8e84ab65531 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -137,6 +137,7 @@ where nested_goals: Vec<(GoalSource, Goal, Option>)>, pub(super) origin_span: I::Span, + root_body_id: Option, // Has this `EvalCtxt` errored out with `NoSolution` in `try_evaluate_added_goals`? // @@ -168,6 +169,7 @@ pub trait SolverDelegateEvalExt: SolverDelegate { &self, goal: Goal::Predicate>, span: ::Span, + body_id: Option<::LocalDefId>, stalled_on: Option>, ) -> Result, NoSolution>; @@ -215,11 +217,13 @@ where &self, goal: Goal, span: I::Span, + body_id: Option, stalled_on: Option>, ) -> Result, NoSolution> { - let result = EvalCtxt::enter_root(self, self.cx().recursion_limit(), span, |ecx| { - ecx.evaluate_goal(GoalSource::Misc, goal, stalled_on) - }); + let result = + EvalCtxt::enter_root(self, self.cx().recursion_limit(), span, body_id, |ecx| { + ecx.evaluate_goal(GoalSource::Misc, goal, stalled_on) + }); match result { Ok(i) => Ok(i), @@ -236,7 +240,7 @@ where goal: Goal::Predicate>, ) -> bool { self.probe(|| { - EvalCtxt::enter_root(self, self.cx().recursion_limit(), I::Span::dummy(), |ecx| { + EvalCtxt::enter_root(self, self.cx().recursion_limit(), I::Span::dummy(), None, |ecx| { ecx.evaluate_goal(GoalSource::Misc, goal, None) }) .is_ok_and(|r| match r.certainty { @@ -259,7 +263,7 @@ where goal: Goal::Predicate>, ) -> bool { self.probe(|| { - EvalCtxt::enter_root(self, root_depth, I::Span::dummy(), |ecx| { + EvalCtxt::enter_root(self, root_depth, I::Span::dummy(), None, |ecx| { ecx.evaluate_goal(GoalSource::Misc, goal, None) }) }) @@ -339,6 +343,7 @@ where delegate: &D, root_depth: usize, origin_span: I::Span, + root_body_id: Option, f: impl FnOnce(&mut EvalCtxt<'_, D>) -> R, ) -> R { let mut search_graph = SearchGraph::new(root_depth); @@ -357,6 +362,7 @@ where var_values: CanonicalVarValues::dummy(), current_goal_kind: CurrentGoalKind::Misc, origin_span, + root_body_id, tainted: Ok(()), opaque_accesses: AccessedOpaques::default(), }; @@ -382,6 +388,7 @@ where search_graph: &'a mut SearchGraph, canonical_input: CanonicalInput, proof_tree_builder: &mut inspect::ProofTreeBuilder, + root_body_id: Option, f: impl FnOnce( &mut EvalCtxt<'_, D>, Goal, @@ -421,6 +428,7 @@ where search_graph, nested_goals: Default::default(), origin_span: I::Span::dummy(), + root_body_id, tainted: Ok(()), inspect: proof_tree_builder.new_evaluation_step(var_values), opaque_accesses: AccessedOpaques::default(), @@ -572,11 +580,12 @@ where TypingMode::ErasedNotCoherence(MayBeErased), ); + let mut inspect = inspect::ProofTreeBuilder::new_noop(self.root_body_id); let (canonical_result, accessed_opaques) = self.search_graph.evaluate_goal( self.cx(), canonical_goal, step_kind, - &mut inspect::ProofTreeBuilder::new_noop(), + &mut inspect, ); let should_rerun = self.should_rerun_after_erased_canonicalization( @@ -599,12 +608,9 @@ where let (orig_values, canonical_goal) = canonicalize_goal(self.delegate, goal, &opaque_types, typing_mode); - let (canonical_result, accessed_opaques) = self.search_graph.evaluate_goal( - self.cx(), - canonical_goal, - step_kind, - &mut inspect::ProofTreeBuilder::new_noop(), - ); + let mut inspect = inspect::ProofTreeBuilder::new_noop(self.root_body_id); + let (canonical_result, accessed_opaques) = + self.search_graph.evaluate_goal(self.cx(), canonical_goal, step_kind, &mut inspect); assert!( !accessed_opaques.might_rerun(), "we run without TypingMode::ErasedNotCoherence, so opaques are available, and we don't retry if the outer typing mode is ErasedNotCoherence: {accessed_opaques:?} after {goal:?}" @@ -1516,7 +1522,7 @@ where dst: I::Ty, assume: I::Const, ) -> Result { - self.delegate.is_transmutable(dst, src, assume) + self.delegate.is_transmutable(dst, src, assume, self.root_body_id) } pub(super) fn replace_bound_vars>( diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs index 1c5d6e0b14c65..e4fc7ac0146e5 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs @@ -86,6 +86,7 @@ where search_graph: outer.search_graph, nested_goals: propagated_nested_goals, origin_span: outer.origin_span, + root_body_id: outer.root_body_id, tainted: outer.tainted, inspect: outer.inspect.take_and_enter_probe(), opaque_accesses: AccessedOpaques::default(), diff --git a/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs b/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs index 4369148baf91d..706378058f69c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs +++ b/compiler/rustc_next_trait_solver/src/solve/inspect/build.rs @@ -28,22 +28,27 @@ where I: Interner, { state: Option>>>, + root_body_id: Option, _infcx: PhantomData, } impl, I: Interner> ProofTreeBuilder { pub(crate) fn new() -> ProofTreeBuilder { - ProofTreeBuilder { state: Some(Box::new(None)), _infcx: PhantomData } + ProofTreeBuilder { state: Some(Box::new(None)), root_body_id: None, _infcx: PhantomData } } - pub(crate) fn new_noop() -> ProofTreeBuilder { - ProofTreeBuilder { state: None, _infcx: PhantomData } + pub(crate) fn new_noop(root_body_id: Option) -> ProofTreeBuilder { + ProofTreeBuilder { state: None, root_body_id, _infcx: PhantomData } } pub(crate) fn is_noop(&self) -> bool { self.state.is_none() } + pub(crate) fn root_body_id(&self) -> Option { + self.root_body_id + } + pub(crate) fn new_evaluation_step( &mut self, var_values: ty::CanonicalVarValues, diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 38b21bfea432a..1e8faf49bbf74 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -140,22 +140,30 @@ where inspect: &mut Self::ProofTreeBuilder, ) -> (QueryResult, AccessedOpaques) { ensure_sufficient_stack(|| { - EvalCtxt::enter_canonical(cx, search_graph, input, inspect, |ecx, goal| { - let result = ecx.compute_goal(goal); - - // if we're in `RerunNonErased`, don't even bother with inspect, - // and immediately return - let result = match result { - Ok(i) => Ok(i), - Err(NoSolutionOrRerunNonErased::NoSolution(NoSolution)) => Err(NoSolution), - Err(NoSolutionOrRerunNonErased::RerunNonErased(e)) => { - return Err(e.into()); - } - }; - - ecx.inspect.query_result(result); - result.map_err(Into::into) - }) + let root_body_id = inspect.root_body_id(); + EvalCtxt::enter_canonical( + cx, + search_graph, + input, + inspect, + root_body_id, + |ecx, goal| { + let result = ecx.compute_goal(goal); + + // if we're in `RerunNonErased`, don't even bother with inspect, + // and immediately return + let result = match result { + Ok(i) => Ok(i), + Err(NoSolutionOrRerunNonErased::NoSolution(NoSolution)) => Err(NoSolution), + Err(NoSolutionOrRerunNonErased::RerunNonErased(e)) => { + return Err(e.into()); + } + }; + + ecx.inspect.query_result(result); + result.map_err(Into::into) + }, + ) }) } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index f78bc3e8b4884..21dc93356c9b7 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -2927,8 +2927,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return (obligation.clone(), trait_predicate); }; + let caller_module = self.tcx.parent_module_from_def_id(obligation.cause.body_id); let is_normalized_yes = matches!( - rustc_transmute::TransmuteTypeEnv::new(self.tcx).is_transmutable( + rustc_transmute::TransmuteTypeEnv::new(self.tcx, caller_module).is_transmutable( trait_ref.args.type_at(1), trait_ref.args.type_at(0), assume, @@ -2985,8 +2986,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let dst = trait_pred.trait_ref.args.type_at(0); let src = trait_pred.trait_ref.args.type_at(1); let err_msg = format!("`{src}` cannot be safely transmuted into `{dst}`"); + let caller_module = self.tcx.parent_module_from_def_id(obligation.cause.body_id); - match rustc_transmute::TransmuteTypeEnv::new(self.infcx.tcx) + match rustc_transmute::TransmuteTypeEnv::new(self.infcx.tcx, caller_module) .is_transmutable(src, dst, assume) { Answer::No(reason) => { diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index bd22ba6b6bf6d..5d039776c8fd9 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use rustc_data_structures::fx::FxHashMap; use rustc_hir::LangItem; -use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId, LocalModDefId}; use rustc_infer::infer::canonical::query_response::make_query_region_constraints; use rustc_infer::infer::canonical::{ Canonical, CanonicalExt as _, CanonicalQueryInput, CanonicalVarKind, CanonicalVarValues, @@ -365,6 +365,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< src: Ty<'tcx>, dst: Ty<'tcx>, assume: ty::Const<'tcx>, + body_id: Option, ) -> Result { // Erase regions because we compute layouts in `rustc_transmute`, // which will ICE for region vars. @@ -374,8 +375,14 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< return Err(NoSolution); }; + let caller_module = body_id + .map(|body_id| self.tcx.parent_module_from_def_id(body_id)) + .unwrap_or(LocalModDefId::CRATE_DEF_ID); + // FIXME(transmutability): This really should be returning nested goals for `Answer::If*` - match rustc_transmute::TransmuteTypeEnv::new(self.0.tcx).is_transmutable(src, dst, assume) { + match rustc_transmute::TransmuteTypeEnv::new(self.0.tcx, caller_module) + .is_transmutable(src, dst, assume) + { rustc_transmute::Answer::Yes => Ok(Certainty::Yes), rustc_transmute::Answer::No(_) | rustc_transmute::Answer::If(_) => Err(NoSolution), } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 78220d4d426b7..06aa9600c8bfc 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -103,6 +103,7 @@ impl<'tcx> ObligationStorage<'tcx> { let result = <&SolverDelegate<'tcx>>::from(infcx).evaluate_root_goal( goal, o.cause.span, + Some(o.cause.body_id), stalled_on.take(), ); matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. })) @@ -205,7 +206,12 @@ where continue; } - let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on); + let result = delegate.evaluate_root_goal( + goal, + obligation.cause.span, + Some(obligation.cause.body_id), + stalled_on, + ); self.inspect_evaluated_obligation(infcx, &obligation, &result); let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result { Ok(result) => result, diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 1ba0bb5f776ba..8c46d392d179d 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -95,6 +95,7 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>( match <&SolverDelegate<'tcx>>::from(infcx).evaluate_root_goal( root_obligation.as_goal(), root_obligation.cause.span, + Some(root_obligation.cause.body_id), None, ) { Ok(GoalEvaluation { diff --git a/compiler/rustc_trait_selection/src/solve/normalize.rs b/compiler/rustc_trait_selection/src/solve/normalize.rs index 97b460fe347d8..d04e7df0c2548 100644 --- a/compiler/rustc_trait_selection/src/solve/normalize.rs +++ b/compiler/rustc_trait_selection/src/solve/normalize.rs @@ -53,7 +53,8 @@ where ty::AliasRelationDirection::Equate, ); let goal = Goal::new(infcx.tcx, at.param_env, predicate); - let result = delegate.evaluate_root_goal(goal, at.cause.span, None)?; + let result = + delegate.evaluate_root_goal(goal, at.cause.span, Some(at.cause.body_id), None)?; let normalized = infcx.resolve_vars_if_possible(infer_term); let stalled_goal = match result.certainty { Certainty::Yes => None, diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs index f24214145bafc..f6d55e8af0840 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs @@ -43,7 +43,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ProvePredicate<'tcx> { ) -> Result { ocx.register_obligation(Obligation::new( ocx.infcx.tcx, - ObligationCause::dummy_with_span(span), + ObligationCause::misc(span, key.value.body_id), key.param_env, key.value.predicate, )); diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 11ce6235eb7ff..736b359da7048 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -362,7 +362,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let src = predicate.trait_ref.args.type_at(1); debug!(?src, ?dst); - let mut transmute_env = rustc_transmute::TransmuteTypeEnv::new(self.infcx.tcx); + let caller_module = self.tcx().parent_module_from_def_id(obligation.cause.body_id); + let mut transmute_env = + rustc_transmute::TransmuteTypeEnv::new(self.infcx.tcx, caller_module); let maybe_transmutable = transmute_env.is_transmutable(src, dst, assume); let fully_flattened = match maybe_transmutable { diff --git a/compiler/rustc_traits/src/type_op.rs b/compiler/rustc_traits/src/type_op.rs index f77e1994cf472..433c826ba412b 100644 --- a/compiler/rustc_traits/src/type_op.rs +++ b/compiler/rustc_traits/src/type_op.rs @@ -94,8 +94,9 @@ fn type_op_prove_predicate<'tcx>( pub fn type_op_prove_predicate_with_cause<'tcx>( ocx: &ObligationCtxt<'_, 'tcx>, key: ParamEnvAnd<'tcx, ProvePredicate<'tcx>>, - cause: ObligationCause<'tcx>, + mut cause: ObligationCause<'tcx>, ) { - let ParamEnvAnd { param_env, value: ProvePredicate { predicate } } = key; + let ParamEnvAnd { param_env, value: ProvePredicate { predicate, body_id } } = key; + cause.body_id = body_id; ocx.register_obligation(Obligation::new(ocx.infcx.tcx, cause, param_env, predicate)); } diff --git a/compiler/rustc_transmute/src/layout/mod.rs b/compiler/rustc_transmute/src/layout/mod.rs index 4e548ff8fe20c..afe6df4598cb6 100644 --- a/compiler/rustc_transmute/src/layout/mod.rs +++ b/compiler/rustc_transmute/src/layout/mod.rs @@ -139,18 +139,20 @@ pub mod rustc { /// A visibility node in the layout. #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] pub enum Def<'tcx> { - Adt(ty::AdtDef<'tcx>), - Variant(&'tcx ty::VariantDef), - Field(&'tcx ty::FieldDef), + Adt(ty::AdtDef<'tcx>, bool), + Variant(&'tcx ty::VariantDef, bool), + Field(&'tcx ty::FieldDef, bool), Primitive, } impl<'tcx> super::Def for Def<'tcx> { fn has_safety_invariants(&self) -> bool { - // Rust presently has no notion of 'unsafe fields', so for now we - // make the conservative assumption that everything besides - // primitive types carry safety invariants. - self != &Self::Primitive + match self { + Self::Adt(_, has_safety_invariants) + | Self::Variant(_, has_safety_invariants) + | Self::Field(_, has_safety_invariants) => *has_safety_invariants, + Self::Primitive => false, + } } } diff --git a/compiler/rustc_transmute/src/layout/tree.rs b/compiler/rustc_transmute/src/layout/tree.rs index d7aeb6b06b82f..49d544e5101a5 100644 --- a/compiler/rustc_transmute/src/layout/tree.rs +++ b/compiler/rustc_transmute/src/layout/tree.rs @@ -105,6 +105,21 @@ where Self::alt([x, y, z]) } + /// A `Tree` whose layout matches that of `std::num::NonZero`. + pub(crate) fn nonzero_char(order: Endian) -> Self { + // This is `char`'s bit validity with `\0` removed: + // - [0x000001, 0x00D7FF] + // - [0x00E000, 0x10FFFF] + const _0: RangeInclusive = 0..=0; + const BYTE: RangeInclusive = 0x00..=0xFF; + + let a = Self::from_big_endian(order, [_0, _0, _0, 0x01..=0xFF]); + let b = Self::from_big_endian(order, [_0, _0, 0x01..=0xD7, BYTE]); + let c = Self::from_big_endian(order, [_0, _0, 0xE0..=0xFF, BYTE]); + let d = Self::from_big_endian(order, [_0, 0x01..=0x10, BYTE, BYTE]); + Self::alt([a, b, c, d]) + } + /// A `Tree` whose layout matches `std::num::NonZeroXxx`. #[allow(dead_code)] pub(crate) fn nonzero(width_in_bytes: u64) -> Self { @@ -250,8 +265,11 @@ where #[cfg(feature = "rustc")] pub(crate) mod rustc { use rustc_abi::{ - FieldIdx, FieldsShape, Layout, Size, TagEncoding, TyAndLayout, VariantIdx, Variants, + BackendRepr, FieldIdx, FieldsShape, Layout, Scalar, Size, TagEncoding, TyAndLayout, + VariantIdx, Variants, }; + use rustc_hir::def_id::DefId; + use rustc_hir::find_attr; use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, LayoutError}; use rustc_middle::ty::{ self, AdtDef, AdtKind, List, Region, ScalarInt, Ty, TyCtxt, TypeVisitableExt, @@ -287,7 +305,11 @@ pub(crate) mod rustc { } impl<'tcx> Tree, Region<'tcx>, Ty<'tcx>> { - pub(crate) fn from_ty(ty: Ty<'tcx>, cx: LayoutCx<'tcx>) -> Result { + pub(crate) fn from_ty( + ty: Ty<'tcx>, + cx: LayoutCx<'tcx>, + caller_module: DefId, + ) -> Result { use rustc_abi::HasDataLayout; let layout = layout_of(cx, ty)?; @@ -316,7 +338,7 @@ pub(crate) mod rustc { Ok(Self::number(width.try_into().unwrap())) } - ty::Tuple(members) => Self::from_tuple((ty, layout), members, cx), + ty::Tuple(members) => Self::from_tuple((ty, layout), members, cx, caller_module), ty::Array(inner_ty, _len) => { let FieldsShape::Array { stride, count } = &layout.fields else { @@ -324,16 +346,41 @@ pub(crate) mod rustc { }; let inner_layout = layout_of(cx, *inner_ty)?; assert_eq!(*stride, inner_layout.size); - let elt = Tree::from_ty(*inner_ty, cx)?; + let elt = Tree::from_ty(*inner_ty, cx, caller_module)?; Ok(std::iter::repeat_n(elt, *count as usize) .fold(Tree::unit(), |tree, elt| tree.then(elt))) } - ty::Adt(adt_def, _args_ref) if !ty.is_box() => match adt_def.adt_kind() { - AdtKind::Struct => Self::from_struct((ty, layout), *adt_def, cx), - AdtKind::Enum => Self::from_enum((ty, layout), *adt_def, cx), - AdtKind::Union => Self::from_union((ty, layout), *adt_def, cx), - }, + ty::Adt(adt_def, args_ref) if !ty.is_box() => { + let scalar = match layout.backend_repr { + BackendRepr::Scalar(scalar) => Some(scalar), + _ => None, + }; + + match adt_def.adt_kind() { + AdtKind::Struct => { + if adt_def.repr().transparent() + && let Some(scalar) = scalar + && !scalar.is_always_valid(&cx) + { + if let Some(tree) = Self::from_transparent_nonzero_struct( + *adt_def, + args_ref, + scalar, + cx, + caller_module, + )? { + return Ok(tree); + } + } + Self::from_struct((ty, layout), *adt_def, cx, caller_module) + } + AdtKind::Enum => Self::from_enum((ty, layout), *adt_def, cx, caller_module), + AdtKind::Union => { + Self::from_union((ty, layout), *adt_def, cx, caller_module) + } + } + } ty::Ref(region, ty, mutability) => { let layout = layout_of(cx, *ty)?; @@ -360,21 +407,69 @@ pub(crate) mod rustc { (ty, layout): (Ty<'tcx>, Layout<'tcx>), members: &'tcx List>, cx: LayoutCx<'tcx>, + caller_module: DefId, ) -> Result { match &layout.fields { FieldsShape::Primitive => { assert_eq!(members.len(), 1); let inner_ty = members[0]; - Self::from_ty(inner_ty, cx) + Self::from_ty(inner_ty, cx, caller_module) } FieldsShape::Arbitrary { offsets, .. } => { assert_eq!(offsets.len(), members.len()); - Self::from_variant(Def::Primitive, None, (ty, layout), layout.size, cx) + Self::from_variant( + Def::Primitive, + None, + (ty, layout), + layout.size, + cx, + caller_module, + ) } FieldsShape::Array { .. } | FieldsShape::Union(_) => Err(Err::NotYetSupported), } } + fn from_transparent_nonzero_struct( + def: AdtDef<'tcx>, + args_ref: ty::GenericArgsRef<'tcx>, + scalar: Scalar, + cx: LayoutCx<'tcx>, + caller_module: DefId, + ) -> Result, Err> { + let variant = def.non_enum_variant(); + // For now, only support `repr(transparent)` types with a single + // field. Technically `repr(transparent)` also works with types with + // any number of zero-sized fields in addition to their single + // non-zero-sized field, but no standard library NonZero type needs + // that extra generality. + let [field] = &variant.fields.as_slice().raw else { + return Err(Err::NotYetSupported); + }; + + let field_ty = + cx.tcx().normalize_erasing_regions(cx.typing_env, field.ty(cx.tcx(), args_ref)); + + let Some(field_tree) = Self::nonzero_scalar_tree(field_ty, scalar, cx)? else { + if find_attr!(cx.tcx(), def.did(), RustcNonnullOptimizationGuaranteed) { + return Err(Err::NotYetSupported); + } + return Ok(None); + }; + + Ok(Some(Self::seq([ + Self::def(Def::Adt( + def, + struct_has_safety_invariants(def.non_enum_variant(), caller_module, cx.tcx()), + )), + Self::def(Def::Field( + field, + field_has_safety_invariants(field, caller_module, cx.tcx()), + )), + field_tree, + ]))) + } + /// Constructs a `Tree` from a struct. /// /// # Panics @@ -384,10 +479,14 @@ pub(crate) mod rustc { (ty, layout): (Ty<'tcx>, Layout<'tcx>), def: AdtDef<'tcx>, cx: LayoutCx<'tcx>, + caller_module: DefId, ) -> Result { assert!(def.is_struct()); - let def = Def::Adt(def); - Self::from_variant(def, None, (ty, layout), layout.size, cx) + let def = Def::Adt( + def, + struct_has_safety_invariants(def.non_enum_variant(), caller_module, cx.tcx()), + ); + Self::from_variant(def, None, (ty, layout), layout.size, cx, caller_module) } /// Constructs a `Tree` from an enum. @@ -399,6 +498,7 @@ pub(crate) mod rustc { (ty, layout): (Ty<'tcx>, Layout<'tcx>), def: AdtDef<'tcx>, cx: LayoutCx<'tcx>, + caller_module: DefId, ) -> Result { assert!(def.is_enum()); @@ -411,13 +511,17 @@ pub(crate) mod rustc { let tag = cx.tcx().tag_for_variant( cx.typing_env.as_query_input((cx.tcx().erase_and_anonymize_regions(ty), index)), ); - let variant_def = Def::Variant(def.variant(index)); + let variant_def = Def::Variant( + def.variant(index), + variant_has_safety_invariants(def.variant(index), caller_module, cx.tcx()), + ); Self::from_variant( variant_def, tag.map(|tag| (tag, index, encoding.unwrap())), (ty, variant_layout), layout.size, cx, + caller_module, ) }; @@ -445,7 +549,7 @@ pub(crate) mod rustc { }, )?; - Ok(Self::def(Def::Adt(def)).then(variants)) + Ok(Self::def(Def::Adt(def, false)).then(variants)) } } } @@ -453,8 +557,9 @@ pub(crate) mod rustc { /// Constructs a `Tree` from a 'variant-like' layout. /// /// A 'variant-like' layout includes those of structs and, of course, - /// enum variants. Pragmatically speaking, this method supports anything - /// with `FieldsShape::Arbitrary`. + /// enum variants. Pragmatically speaking, this method supports ADT + /// layouts with either `FieldsShape::Primitive` or + /// `FieldsShape::Arbitrary`. /// /// Note: This routine assumes that the optional `tag` is the first /// field, and enum callers should check that `tag_field` is, in fact, @@ -465,18 +570,29 @@ pub(crate) mod rustc { (ty, layout): (Ty<'tcx>, Layout<'tcx>), total_size: Size, cx: LayoutCx<'tcx>, + caller_module: DefId, ) -> Result { - // This constructor does not support non-`FieldsShape::Arbitrary` - // layouts. - let FieldsShape::Arbitrary { offsets, in_memory_order } = layout.fields() else { - return Err(Err::NotYetSupported); - }; - // When this function is invoked with enum variants, // `ty_and_layout.size` does not encompass the entire size of the // enum. We rely on `total_size` for this. assert!(layout.size <= total_size); + if matches!(layout.fields(), FieldsShape::Primitive) { + return Self::from_primitive_variant( + def, + tag, + (ty, layout), + total_size, + cx, + caller_module, + ); + } + + // The remaining variant layouts must have explicit field offsets. + let FieldsShape::Arbitrary { offsets, in_memory_order } = layout.fields() else { + return Err(Err::NotYetSupported); + }; + let mut size = Size::ZERO; let mut struct_tree = Self::def(def); @@ -503,9 +619,18 @@ pub(crate) mod rustc { let field_ty = ty_field(cx, (ty, layout), field_idx); let field_layout = layout_of(cx, field_ty)?; - let field_tree = Self::from_ty(field_ty, cx)?; + let field_tree = Self::from_ty(field_ty, cx, caller_module)?; + + struct_tree = struct_tree.then(padding); - struct_tree = struct_tree.then(padding).then(field_tree); + if let Some(field) = field_def((ty, layout), field_idx) { + struct_tree = struct_tree.then(Self::def(Def::Field( + field, + field_has_safety_invariants(field, caller_module, cx.tcx()), + ))); + } + + struct_tree = struct_tree.then(field_tree); size += padding_needed + field_layout.size; } @@ -517,6 +642,46 @@ pub(crate) mod rustc { Ok(struct_tree.then(trailing_padding)) } + fn from_primitive_variant( + def: Def<'tcx>, + tag: Option<(ScalarInt, VariantIdx, TagEncoding)>, + (ty, layout): (Ty<'tcx>, Layout<'tcx>), + total_size: Size, + cx: LayoutCx<'tcx>, + caller_module: DefId, + ) -> Result { + if tag.is_some() { + return Err(Err::NotYetSupported); + } + + let ty::Adt(adt_def, _) = ty.kind() else { + return Err(Err::NotYetSupported); + }; + let Variants::Single { index } = layout.variants() else { + return Err(Err::NotYetSupported); + }; + + let mut size = Size::ZERO; + let mut variant_tree = Self::def(def); + + for (field_idx, field) in adt_def.variant(*index).fields.iter_enumerated() { + let field_ty = ty_field(cx, (ty, layout), field_idx); + let field_layout = layout_of(cx, field_ty)?; + let field_tree = Self::from_ty(field_ty, cx, caller_module)?; + + variant_tree = variant_tree.then(Self::def(Def::Field( + field, + field_has_safety_invariants(field, caller_module, cx.tcx()), + ))); + variant_tree = variant_tree.then(field_tree); + size += field_layout.size; + } + + let padding_needed = total_size - size; + let trailing_padding = Self::padding(padding_needed.bytes_usize()); + Ok(variant_tree.then(trailing_padding)) + } + /// Constructs a `Tree` representing the value of a enum tag. fn from_tag(tag: ScalarInt, tcx: TyCtxt<'tcx>) -> Self { use rustc_abi::Endian; @@ -545,6 +710,7 @@ pub(crate) mod rustc { (ty, layout): (Ty<'tcx>, Layout<'tcx>), def: AdtDef<'tcx>, cx: LayoutCx<'tcx>, + caller_module: DefId, ) -> Result { assert!(def.is_union()); @@ -560,7 +726,7 @@ pub(crate) mod rustc { |fields, (idx, _field_def)| { let field_ty = ty_field(cx, (ty, layout), idx); let field_layout = layout_of(cx, field_ty)?; - let field = Self::from_ty(field_ty, cx)?; + let field = Self::from_ty(field_ty, cx, caller_module)?; let trailing_padding_needed = layout.size - field_layout.size; let trailing_padding = Self::padding(trailing_padding_needed.bytes_usize()); let field_and_padding = field.then(trailing_padding); @@ -568,10 +734,85 @@ pub(crate) mod rustc { }, )?; - Ok(Self::def(Def::Adt(def)).then(fields)) + Ok(Self::def(Def::Adt(def, false)).then(fields)) + } + + fn nonzero_scalar_tree( + field_ty: Ty<'tcx>, + scalar: Scalar, + cx: LayoutCx<'tcx>, + ) -> Result, Err> { + let scalar_size = scalar.size(&cx); + let valid_range = scalar.valid_range(&cx); + let base_ty = transparent_scalar_base_ty(field_ty, cx)?; + + match *base_ty.kind() { + ty::Int(_) | ty::Uint(_) + if valid_range.start == 1 + && valid_range.end == scalar_size.unsigned_int_max() => + { + Ok(Some(Self::nonzero(scalar_size.bytes()))) + } + ty::Char if valid_range.start == 1 && valid_range.end == 0x10FFFF => { + Ok(Some(Self::nonzero_char(cx.tcx().data_layout.endian.into()))) + } + _ => Ok(None), + } + } + } + + fn transparent_scalar_base_ty<'tcx>(ty: Ty<'tcx>, cx: LayoutCx<'tcx>) -> Result, Err> { + let layout = layout_of(cx, ty)?; + match ty.kind() { + ty::Pat(base, _) => Ok(*base), + ty::Adt(def, args) if def.repr().transparent() => { + let variant = def.non_enum_variant(); + let [field] = &variant.fields.as_slice().raw else { return Ok(ty) }; + let field_ty = + cx.tcx().normalize_erasing_regions(cx.typing_env, field.ty(cx.tcx(), args)); + let field_layout = layout_of(cx, field_ty)?; + if field_layout.size == layout.size + && matches!(layout.backend_repr, BackendRepr::Scalar(_)) + && matches!(field_layout.backend_repr, BackendRepr::Scalar(_)) + { + transparent_scalar_base_ty(field_ty, cx) + } else { + Ok(ty) + } + } + _ => Ok(ty), } } + fn struct_has_safety_invariants<'tcx>( + variant: &'tcx ty::VariantDef, + caller_module: DefId, + tcx: TyCtxt<'tcx>, + ) -> bool { + variant.fields.is_empty() + || variant.field_list_has_applicable_non_exhaustive() + || variant.ctor_def_id().is_some_and(|ctor_def_id| { + !tcx.visibility(ctor_def_id).is_accessible_from(caller_module, tcx) + }) + } + + fn variant_has_safety_invariants<'tcx>( + variant: &'tcx ty::VariantDef, + caller_module: DefId, + tcx: TyCtxt<'tcx>, + ) -> bool { + variant.field_list_has_applicable_non_exhaustive() + || !tcx.visibility(variant.def_id).is_accessible_from(caller_module, tcx) + } + + fn field_has_safety_invariants<'tcx>( + field: &'tcx ty::FieldDef, + caller_module: DefId, + tcx: TyCtxt<'tcx>, + ) -> bool { + field.safety.is_unsafe() || !field.vis.is_accessible_from(caller_module, tcx) + } + fn ty_field<'tcx>( cx: LayoutCx<'tcx>, (ty, layout): (Ty<'tcx>, Layout<'tcx>), @@ -604,6 +845,21 @@ pub(crate) mod rustc { } } + fn field_def<'tcx>( + (ty, layout): (Ty<'tcx>, Layout<'tcx>), + i: FieldIdx, + ) -> Option<&'tcx ty::FieldDef> { + match ty.kind() { + ty::Adt(def, _args) => match layout.variants { + Variants::Single { index } => Some(&def.variant(index).fields[i]), + Variants::Empty => panic!("there is no field in Variants::Empty types"), + Variants::Multiple { .. } => None, + }, + ty::Tuple(_) => None, + _ => None, + } + } + fn ty_variant<'tcx>( cx: LayoutCx<'tcx>, (ty, layout): (Ty<'tcx>, Layout<'tcx>), diff --git a/compiler/rustc_transmute/src/lib.rs b/compiler/rustc_transmute/src/lib.rs index 732881f12d2cb..9845fe3d80451 100644 --- a/compiler/rustc_transmute/src/lib.rs +++ b/compiler/rustc_transmute/src/lib.rs @@ -88,18 +88,20 @@ pub enum Reason { #[cfg(feature = "rustc")] mod rustc { + use rustc_hir::def_id::LocalModDefId; use rustc_hir::lang_items::LangItem; use rustc_middle::ty::{Const, Region, Ty, TyCtxt}; use super::*; + use crate::maybe_transmutable::query_context::rustc::RustcQueryContext; pub struct TransmuteTypeEnv<'tcx> { - tcx: TyCtxt<'tcx>, + context: RustcQueryContext<'tcx>, } impl<'tcx> TransmuteTypeEnv<'tcx> { - pub fn new(tcx: TyCtxt<'tcx>) -> Self { - Self { tcx } + pub fn new(tcx: TyCtxt<'tcx>, caller_module: LocalModDefId) -> Self { + Self { context: RustcQueryContext { tcx, caller_module: caller_module.to_def_id() } } } pub fn is_transmutable( @@ -108,7 +110,7 @@ mod rustc { dst: Ty<'tcx>, assume: crate::Assume, ) -> crate::Answer, Ty<'tcx>> { - crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, self.tcx) + crate::maybe_transmutable::MaybeTransmutableQuery::new(src, dst, assume, self.context) .answer() } } diff --git a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs index b748611be383f..a7b88bba4c3f8 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs @@ -32,27 +32,30 @@ where #[cfg(feature = "rustc")] mod rustc { use rustc_middle::ty::layout::LayoutCx; - use rustc_middle::ty::{Ty, TyCtxt, TypingEnv}; + use rustc_middle::ty::{Ty, TypingEnv}; use super::*; use crate::layout::tree::rustc::Err; + use crate::maybe_transmutable::query_context::rustc::RustcQueryContext; - impl<'tcx> MaybeTransmutableQuery, TyCtxt<'tcx>> { + impl<'tcx> MaybeTransmutableQuery, RustcQueryContext<'tcx>> { /// This method begins by converting `src` and `dst` from `Ty`s to `Tree`s, /// then computes an answer using those trees. #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] pub(crate) fn answer( self, - ) -> Answer< as QueryContext>::Region, as QueryContext>::Type> - { + ) -> Answer< + as QueryContext>::Region, + as QueryContext>::Type, + > { let Self { src, dst, assume, context } = self; - let layout_cx = LayoutCx::new(context, TypingEnv::fully_monomorphized()); + let layout_cx = LayoutCx::new(context.tcx, TypingEnv::fully_monomorphized()); // Convert `src` and `dst` from their rustc representations, to `Tree`-based // representations. - let src = Tree::from_ty(src, layout_cx); - let dst = Tree::from_ty(dst, layout_cx); + let src = Tree::from_ty(src, layout_cx, context.caller_module); + let dst = Tree::from_ty(dst, layout_cx, context.caller_module); match (src, dst) { (Err(Err::TypeError(_)), _) | (_, Err(Err::TypeError(_))) => { diff --git a/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs b/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs index 6be0cb1190436..e5fc0df488d22 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/query_context.rs @@ -45,14 +45,27 @@ pub(crate) mod test { } #[cfg(feature = "rustc")] -mod rustc { +pub(crate) mod rustc { + use rustc_hir::def_id::DefId; use rustc_middle::ty::{Region, Ty, TyCtxt}; use super::*; + #[derive(Clone, Copy)] + pub(crate) struct RustcQueryContext<'tcx> { + pub(crate) tcx: TyCtxt<'tcx>, + pub(crate) caller_module: DefId, + } + impl<'tcx> super::QueryContext for TyCtxt<'tcx> { type Def = layout::rustc::Def<'tcx>; type Region = Region<'tcx>; type Type = Ty<'tcx>; } + + impl<'tcx> super::QueryContext for RustcQueryContext<'tcx> { + type Def = layout::rustc::Def<'tcx>; + type Region = Region<'tcx>; + type Type = Ty<'tcx>; + } } diff --git a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs index 8440ace260883..dc6185a4ff710 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs @@ -361,6 +361,56 @@ mod char { } } } + + #[test] + fn should_permit_valid_nonzero_char_transmutation() { + for order in [Endian::Big, Endian::Little] { + use Answer::*; + let char_layout = layout::Tree::::char(order); + let nonzero_char_layout = layout::Tree::::nonzero_char(order); + let nonzero_u32_layout = layout::Tree::::nonzero(4); + + assert_eq!( + is_transmutable(&nonzero_char_layout, &char_layout, Assume::default()), + Yes, + "endian:{order:?}", + ); + assert_eq!( + is_transmutable(&char_layout, &nonzero_char_layout, Assume::default()), + No(Reason::DstIsBitIncompatible), + "endian:{order:?}", + ); + assert_eq!( + is_transmutable(&nonzero_char_layout, &nonzero_u32_layout, Assume::default()), + Yes, + "endian:{order:?}", + ); + assert_eq!( + is_transmutable(&nonzero_u32_layout, &nonzero_char_layout, Assume::default()), + No(Reason::DstIsBitIncompatible), + "endian:{order:?}", + ); + + let no = No(Reason::DstIsBitIncompatible); + for (src, answer) in [ + (0u32, no.clone()), + (1, Yes), + (0xD7FF, Yes), + (0xD800, no.clone()), + (0xDFFF, no.clone()), + (0xE000, Yes), + (0x10FFFF, Yes), + (0x110000, no.clone()), + (0xFFFFFFFF, no), + ] { + let src_layout = + layout::tree::Tree::::from_big_endian(order, src.to_be_bytes()); + + let a = is_transmutable(&src_layout, &nonzero_char_layout, Assume::default()); + assert_eq!(a, answer, "endian:{order:?},\nsrc:{src:x}"); + } + } + } } mod nonzero { diff --git a/library/core/src/mem/transmutability.rs b/library/core/src/mem/transmutability.rs index e26c1b8fa1e19..7166a18755ff3 100644 --- a/library/core/src/mem/transmutability.rs +++ b/library/core/src/mem/transmutability.rs @@ -208,13 +208,15 @@ pub struct Assume { /// /// let src: u8 = 3; /// - /// struct EvenU8 { - /// // SAFETY: `val` must be an even number. - /// val: u8, + /// mod owner { + /// pub struct EvenU8 { + /// // SAFETY: `val` must be an even number. + /// val: u8, + /// } /// } /// /// // SAFETY: No safety obligations. - /// let dst: EvenU8 = unsafe { + /// let dst: owner::EvenU8 = unsafe { /// <_ as TransmuteFrom<_>>::transmute(src) /// }; /// ``` @@ -229,12 +231,20 @@ pub struct Assume { /// /// let src: u8 = 42; /// - /// struct EvenU8 { - /// // SAFETY: `val` must be an even number. - /// val: u8, + /// mod owner { + /// pub struct EvenU8 { + /// // SAFETY: `val` must be an even number. + /// val: u8, + /// } + /// + /// impl EvenU8 { + /// pub fn get(&self) -> u8 { + /// self.val + /// } + /// } /// } /// - /// let maybe_dst: Option = if src % 2 == 0 { + /// let maybe_dst: Option = if src % 2 == 0 { /// // SAFETY: We have checked above that the value of `src` is even. /// Some(unsafe { /// <_ as TransmuteFrom<_, { Assume::SAFETY }>>::transmute(src) @@ -243,7 +253,7 @@ pub struct Assume { /// None /// }; /// - /// assert!(matches!(maybe_dst, Some(EvenU8 { val: 42 }))); + /// assert_eq!(maybe_dst.map(|dst| dst.get()), Some(42)); /// ``` pub safety: bool, diff --git a/tests/ui/transmutability/nonzero-pass.rs b/tests/ui/transmutability/nonzero-pass.rs new file mode 100644 index 0000000000000..0bbfdc3b79215 --- /dev/null +++ b/tests/ui/transmutability/nonzero-pass.rs @@ -0,0 +1,137 @@ +//@ check-pass + +#![feature(transmutability)] +#![feature(unsafe_fields)] +#![allow(dead_code, incomplete_features)] + +mod assert { + use std::mem::{Assume, TransmuteFrom}; + + pub fn nothing() + where + Dst: TransmuteFrom, + { + } + + pub fn safety() + where + Dst: TransmuteFrom, + { + } + + pub fn safety_and_validity() + where + Dst: TransmuteFrom, + { + } +} + +macro_rules! assert_integer_nonzero { + ($int:ty, $nonzero:ty) => { + assert::safety::<$nonzero, $int>(); + assert::safety::<$nonzero, $nonzero>(); + assert::safety_and_validity::<$int, $nonzero>(); + + assert::safety::, $int>(); + assert::safety::<$int, Option<$nonzero>>(); + assert::safety::, Option<$nonzero>>(); + + assert::safety::<[$nonzero; 3], [$int; 3]>(); + assert::safety_and_validity::<[$int; 3], [$nonzero; 3]>(); + assert::safety::<($nonzero, $nonzero), ($int, $int)>(); + assert::safety_and_validity::<($int, $int), ($nonzero, $nonzero)>(); + }; +} + +macro_rules! assert_same_width_integer_nonzeros { + ($uint:ty, $int:ty, $nonzero_uint:ty, $nonzero_int:ty) => { + assert::safety::<$nonzero_uint, $int>(); + assert::safety::<$nonzero_int, $uint>(); + assert::safety::<$nonzero_uint, $nonzero_int>(); + assert::safety::<$nonzero_int, $nonzero_uint>(); + + assert::safety::, $int>(); + assert::safety::, $uint>(); + assert::safety::<$uint, Option<$nonzero_int>>(); + assert::safety::<$int, Option<$nonzero_uint>>(); + }; +} + +#[repr(C)] +struct PublicField { + pub field: u8, +} + +#[repr(C)] +struct PublicUnsafeField { + pub unsafe field: u8, +} + +mod owner { + #[repr(C)] + pub struct VisibleFromChild { + field: u8, + } + + pub mod child { + use super::VisibleFromChild; + use crate::assert; + + pub fn check() { + assert::nothing::(); + assert::nothing::(); + } + } +} + +fn main() { + use std::num::{ + NonZero, NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, + NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, + }; + + type NonZeroChar = NonZero; + + assert_integer_nonzero!(u8, NonZeroU8); + assert_integer_nonzero!(u16, NonZeroU16); + assert_integer_nonzero!(u32, NonZeroU32); + assert_integer_nonzero!(u64, NonZeroU64); + assert_integer_nonzero!(u128, NonZeroU128); + assert_integer_nonzero!(usize, NonZeroUsize); + + assert_integer_nonzero!(i8, NonZeroI8); + assert_integer_nonzero!(i16, NonZeroI16); + assert_integer_nonzero!(i32, NonZeroI32); + assert_integer_nonzero!(i64, NonZeroI64); + assert_integer_nonzero!(i128, NonZeroI128); + assert_integer_nonzero!(isize, NonZeroIsize); + + assert_same_width_integer_nonzeros!(u8, i8, NonZeroU8, NonZeroI8); + assert_same_width_integer_nonzeros!(u16, i16, NonZeroU16, NonZeroI16); + assert_same_width_integer_nonzeros!(u32, i32, NonZeroU32, NonZeroI32); + assert_same_width_integer_nonzeros!(u64, i64, NonZeroU64, NonZeroI64); + assert_same_width_integer_nonzeros!(u128, i128, NonZeroU128, NonZeroI128); + assert_same_width_integer_nonzeros!(usize, isize, NonZeroUsize, NonZeroIsize); + + assert::safety::(); + assert::safety::(); + assert::safety::(); + assert::safety::, char>(); + assert::safety::>(); + assert::safety::, u32>(); + assert::safety::, Option>(); + assert::safety_and_validity::(); + assert::safety_and_validity::(); + + assert::safety::<[NonZeroChar; 3], [char; 3]>(); + assert::safety::<[NonZeroChar; 3], [u32; 3]>(); + assert::safety_and_validity::<[char; 3], [NonZeroChar; 3]>(); + assert::safety::<(NonZeroChar, NonZeroChar), (char, char)>(); + + assert::nothing::(); + assert::nothing::(); + owner::child::check(); + + assert::safety::(); + assert::safety::(); +} diff --git a/tests/ui/transmutability/nonzero.rs b/tests/ui/transmutability/nonzero.rs new file mode 100644 index 0000000000000..bba986f1a2205 --- /dev/null +++ b/tests/ui/transmutability/nonzero.rs @@ -0,0 +1,73 @@ +#![feature(transmutability)] +#![allow(dead_code)] + +mod assert { + use std::mem::{Assume, TransmuteFrom}; + + pub fn nothing() + where + Dst: TransmuteFrom, + { + } + + pub fn safety() + where + Dst: TransmuteFrom, + { + } +} + +fn main() { + use std::num::{ + NonZero, NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, + NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, + }; + + type NonZeroChar = NonZero; + + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::nothing::(); //~ ERROR: cannot be safely transmuted + + assert::safety::<[u8; 3], [NonZeroU8; 3]>(); //~ ERROR: cannot be safely transmuted + assert::safety::<[i16; 3], [NonZeroI16; 3]>(); //~ ERROR: cannot be safely transmuted + assert::safety::<[char; 3], [NonZeroChar; 3]>(); //~ ERROR: cannot be safely transmuted + + assert::safety::<(u8, u16), (NonZeroU8, NonZeroU16)>(); //~ ERROR: cannot be safely transmuted + assert::safety::<(char, u32), (NonZeroChar, NonZeroU32)>(); //~ ERROR: cannot be safely transmuted + + assert::safety::>(); //~ ERROR: cannot be safely transmuted + assert::safety::>(); //~ ERROR: cannot be safely transmuted + + assert::safety::(); //~ ERROR: cannot be safely transmuted + assert::safety::(); //~ ERROR: cannot be safely transmuted +} diff --git a/tests/ui/transmutability/nonzero.stderr b/tests/ui/transmutability/nonzero.stderr new file mode 100644 index 0000000000000..73214530aa3c8 --- /dev/null +++ b/tests/ui/transmutability/nonzero.stderr @@ -0,0 +1,558 @@ +error[E0277]: `u8` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:28:26 + | +LL | assert::safety::(); + | ^^^^^^^^^ at least one value of `u8` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `u16` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:29:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^ at least one value of `u16` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `u32` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:30:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^ at least one value of `u32` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `u64` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:31:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^ at least one value of `u64` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `u128` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:32:28 + | +LL | assert::safety::(); + | ^^^^^^^^^^^ at least one value of `u128` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `usize` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:33:29 + | +LL | assert::safety::(); + | ^^^^^^^^^^^^ at least one value of `usize` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `i8` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:35:26 + | +LL | assert::safety::(); + | ^^^^^^^^^ at least one value of `i8` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `i16` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:36:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^ at least one value of `i16` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `i32` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:37:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^ at least one value of `i32` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `i64` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:38:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^ at least one value of `i64` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `i128` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:39:28 + | +LL | assert::safety::(); + | ^^^^^^^^^^^ at least one value of `i128` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `isize` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:40:29 + | +LL | assert::safety::(); + | ^^^^^^^^^^^^ at least one value of `isize` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:42:34 + | +LL | assert::nothing::(); + | ^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:43:35 + | +LL | assert::nothing::(); + | ^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:44:35 + | +LL | assert::nothing::(); + | ^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:45:35 + | +LL | assert::nothing::(); + | ^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:46:36 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:47:37 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:49:34 + | +LL | assert::nothing::(); + | ^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:50:35 + | +LL | assert::nothing::(); + | ^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:51:35 + | +LL | assert::nothing::(); + | ^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:52:35 + | +LL | assert::nothing::(); + | ^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:53:36 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:54:37 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `char` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:56:28 + | +LL | assert::safety::(); + | ^^^^^^^^^^^ at least one value of `char` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `u32` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:57:27 + | +LL | assert::safety::(); + | ^^^^^^^^^^^ at least one value of `u32` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:58:34 + | +LL | assert::safety::(); + | ^^^^^^^^^^^ at least one value of `NonZero` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `NonZero` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:59:36 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^ `NonZero` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/nonzero.rs:9:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `[u8; 3]` cannot be safely transmuted into `[NonZero; 3]` + --> $DIR/nonzero.rs:61:31 + | +LL | assert::safety::<[u8; 3], [NonZeroU8; 3]>(); + | ^^^^^^^^^^^^^^ at least one value of `[u8; 3]` isn't a bit-valid value of `[NonZero; 3]` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `[i16; 3]` cannot be safely transmuted into `[NonZero; 3]` + --> $DIR/nonzero.rs:62:32 + | +LL | assert::safety::<[i16; 3], [NonZeroI16; 3]>(); + | ^^^^^^^^^^^^^^^ at least one value of `[i16; 3]` isn't a bit-valid value of `[NonZero; 3]` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `[char; 3]` cannot be safely transmuted into `[NonZero; 3]` + --> $DIR/nonzero.rs:63:33 + | +LL | assert::safety::<[char; 3], [NonZeroChar; 3]>(); + | ^^^^^^^^^^^^^^^^ at least one value of `[char; 3]` isn't a bit-valid value of `[NonZero; 3]` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `(u8, u16)` cannot be safely transmuted into `(NonZero, NonZero)` + --> $DIR/nonzero.rs:65:33 + | +LL | assert::safety::<(u8, u16), (NonZeroU8, NonZeroU16)>(); + | ^^^^^^^^^^^^^^^^^^^^^^^ at least one value of `(u8, u16)` isn't a bit-valid value of `(NonZero, NonZero)` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `(char, u32)` cannot be safely transmuted into `(NonZero, NonZero)` + --> $DIR/nonzero.rs:66:35 + | +LL | assert::safety::<(char, u32), (NonZeroChar, NonZeroU32)>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ at least one value of `(char, u32)` isn't a bit-valid value of `(NonZero, NonZero)` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `u32` cannot be safely transmuted into `Option>` + --> $DIR/nonzero.rs:68:27 + | +LL | assert::safety::>(); + | ^^^^^^^^^^^^^^^^^^^ at least one value of `u32` isn't a bit-valid value of `Option>` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `NonZero` cannot be safely transmuted into `Option>` + --> $DIR/nonzero.rs:69:34 + | +LL | assert::safety::>(); + | ^^^^^^^^^^^^^^^^^^^ at least one value of `NonZero` isn't a bit-valid value of `Option>` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `bool` cannot be safely transmuted into `NonZero` + --> $DIR/nonzero.rs:71:28 + | +LL | assert::safety::(); + | ^^^^^^^^^ at least one value of `bool` isn't a bit-valid value of `NonZero` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error[E0277]: `NonZero` cannot be safely transmuted into `bool` + --> $DIR/nonzero.rs:72:33 + | +LL | assert::safety::(); + | ^^^^ at least one value of `NonZero` isn't a bit-valid value of `bool` + | +note: required by a bound in `safety` + --> $DIR/nonzero.rs:15:14 + | +LL | pub fn safety() + | ------ required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `safety` + +error: aborting due to 37 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/safety/field-visibility.rs b/tests/ui/transmutability/safety/field-visibility.rs new file mode 100644 index 0000000000000..e70f17cc07b36 --- /dev/null +++ b/tests/ui/transmutability/safety/field-visibility.rs @@ -0,0 +1,49 @@ +#![feature(transmutability)] +#![feature(unsafe_fields)] +#![allow(dead_code, incomplete_features)] + +mod assert { + use std::mem::{Assume, TransmuteFrom}; + + pub fn nothing() + where + Dst: TransmuteFrom, + { + } + + pub fn safety() + where + Dst: TransmuteFrom, + { + } +} + +mod owner { + #[repr(C)] + pub struct PublicField { + pub field: u8, + } + + #[repr(C)] + pub struct PrivateField { + field: u8, + } + + #[repr(C)] + pub struct PublicUnsafeField { + pub unsafe field: u8, + } +} + +fn main() { + assert::nothing::(); + assert::nothing::(); + + assert::nothing::(); //~ ERROR cannot be safely transmuted + assert::safety::(); + assert::nothing::(); + + assert::nothing::(); //~ ERROR cannot be safely transmuted + assert::safety::(); + assert::nothing::(); +} diff --git a/tests/ui/transmutability/safety/field-visibility.stderr b/tests/ui/transmutability/safety/field-visibility.stderr new file mode 100644 index 0000000000000..0b03506b92ee3 --- /dev/null +++ b/tests/ui/transmutability/safety/field-visibility.stderr @@ -0,0 +1,33 @@ +error[E0277]: `u8` cannot be safely transmuted into `PrivateField` + --> $DIR/field-visibility.rs:42:27 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^^^^^^^^^ `PrivateField` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/field-visibility.rs:10:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error[E0277]: `u8` cannot be safely transmuted into `PublicUnsafeField` + --> $DIR/field-visibility.rs:46:27 + | +LL | assert::nothing::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ `PublicUnsafeField` may carry safety invariants + | +note: required by a bound in `nothing` + --> $DIR/field-visibility.rs:10:14 + | +LL | pub fn nothing() + | ------- required by a bound in this function +LL | where +LL | Dst: TransmuteFrom, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `nothing` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/transmutability/safety/should_reject_if_ref_src_has_safety_invariant.rs b/tests/ui/transmutability/safety/should_reject_if_ref_src_has_safety_invariant.rs index 16d163d5420b3..203f77e9058b1 100644 --- a/tests/ui/transmutability/safety/should_reject_if_ref_src_has_safety_invariant.rs +++ b/tests/ui/transmutability/safety/should_reject_if_ref_src_has_safety_invariant.rs @@ -2,7 +2,7 @@ //! be rejected if the source potentially carries safety invariants. #![crate_type = "lib"] -#![feature(transmutability)] +#![feature(transmutability, unsafe_fields)] #![allow(dead_code)] mod assert { @@ -17,7 +17,7 @@ mod assert { fn test() { #[repr(C)] struct Src { - non_zero: u8, + pub unsafe non_zero: u8, } type Dst = u8; assert::is_transmutable::<&mut Src, &mut Dst>(); //~ ERROR cannot be safely transmuted