Skip to content

Commit 0dc4ac5

Browse files
committed
[macros] Support shrinking value transmutes
In `transmute!`, support an `#![allow(shrink)]` attribute which is invoked as follows: transmute!(#![allow(shrink)] src); When this attribute is provided, `transmute!` will permit shrinking transmutes, in which the destination value may be smaller than the source value. gherrit-pr-id: I46b18b4b1d10507b7e1d2e01b09dc4960cfcdce1
1 parent 71bc399 commit 0dc4ac5

19 files changed

+190
-172
lines changed

src/macros.rs

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,39 +51,58 @@
5151
/// This macro can be invoked in `const` contexts.
5252
#[macro_export]
5353
macro_rules! transmute {
54-
($e:expr) => {{
55-
// NOTE: This must be a macro (rather than a function with trait bounds)
56-
// because there's no way, in a generic context, to enforce that two
57-
// types have the same size. `core::mem::transmute` uses compiler magic
58-
// to enforce this so long as the types are concrete.
59-
60-
let e = $e;
54+
// NOTE: This must be a macro (rather than a function with trait bounds)
55+
// because there's no way, in a generic context, to enforce that two types
56+
// have the same size. `core::mem::transmute` uses compiler magic to enforce
57+
// this so long as the types are concrete.
58+
(#![allow(shrink)] $e:expr) => {{
59+
let mut e = $e;
6160
if false {
62-
// This branch, though never taken, ensures that the type of `e` is
63-
// `IntoBytes` and that the type of this macro invocation expression
64-
// is `FromBytes`.
61+
$crate::__transmute_inner!(@assert_into_bytes_from_bytes e)
62+
} else {
63+
use $crate::util::macro_util::core_reexport::mem::ManuallyDrop;
64+
65+
#[repr(C)]
66+
union Transmute<Src, Dst> {
67+
src: ManuallyDrop<Src>,
68+
dst: ManuallyDrop<Dst>,
69+
}
6570

66-
struct AssertIsIntoBytes<T: $crate::IntoBytes>(T);
67-
let _ = AssertIsIntoBytes(e);
71+
// TODO: Update this safety comment.
72+
//
73+
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
74+
// the type of this macro invocation expression have the same size.
75+
// We know this transmute is safe thanks to the `IntoBytes` and
76+
// `FromBytes` bounds enforced by the `false` branch.
77+
let u: Transmute<_, _> = unsafe {
78+
// Clippy: We can't annotate the types; this macro is designed
79+
// to infer the types from the calling context.
80+
#[allow(clippy::missing_transmute_annotations, unnecessary_transmutes)]
81+
$crate::util::macro_util::core_reexport::mem::transmute(e)
82+
};
6883

69-
struct AssertIsFromBytes<U: $crate::FromBytes>(U);
70-
#[allow(unused, unreachable_code)]
71-
let u = AssertIsFromBytes(loop {});
72-
u.0
84+
if false {
85+
// SAFETY: This code is never executed.
86+
e = ManuallyDrop::into_inner(unsafe { u.src });
87+
// Suppress the `unused_assignments` lint on the previous line.
88+
let _ = e;
89+
loop {}
90+
} else {
91+
// TODO: Safety comment
92+
let dst = unsafe { u.dst };
93+
$crate::util::macro_util::must_use(ManuallyDrop::into_inner(dst))
94+
}
95+
}
96+
}};
97+
($e:expr) => {{
98+
let e = $e;
99+
if false {
100+
$crate::__transmute_inner!(@assert_into_bytes_from_bytes e)
73101
} else {
74102
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
75103
// the type of this macro invocation expression have the same size.
76104
// We know this transmute is safe thanks to the `IntoBytes` and
77105
// `FromBytes` bounds enforced by the `false` branch.
78-
//
79-
// We use this reexport of `core::mem::transmute` because we know it
80-
// will always be available for crates which are using the 2015
81-
// edition of Rust. By contrast, if we were to use
82-
// `std::mem::transmute`, this macro would not work for such crates
83-
// in `no_std` contexts, and if we were to use
84-
// `core::mem::transmute`, this macro would not work in `std`
85-
// contexts in which `core` was not manually imported. This is not a
86-
// problem for 2018 edition crates.
87106
let u = unsafe {
88107
// Clippy: We can't annotate the types; this macro is designed
89108
// to infer the types from the calling context.
@@ -92,7 +111,29 @@ macro_rules! transmute {
92111
};
93112
$crate::util::macro_util::must_use(u)
94113
}
95-
}}
114+
}};
115+
}
116+
117+
#[macro_export]
118+
#[doc(hidden)]
119+
macro_rules! __transmute_inner {
120+
(@assert_into_bytes_from_bytes $e:expr) => {{
121+
// This branch, though never taken, ensures that the type of `e` is
122+
// `IntoBytes` and that the type of the outer macro invocation
123+
// expression is `FromBytes`.
124+
125+
fn transmute<Src, Dst>(src: Src) -> Dst
126+
where
127+
Src: $crate::IntoBytes,
128+
Dst: $crate::FromBytes,
129+
{
130+
let _ = src;
131+
loop {}
132+
}
133+
loop {}
134+
#[allow(unreachable_code)]
135+
transmute($e)
136+
}};
96137
}
97138

98139
/// Safely transmutes a mutable or immutable reference of one type to an
@@ -1046,6 +1087,10 @@ mod tests {
10461087
let x: [u8; 8] = transmute!(array_of_arrays);
10471088
assert_eq!(x, array_of_u8s);
10481089

1090+
// Test that memory is transmuted as expected when shrinking.
1091+
let x: [[u8; 2]; 3] = transmute!(#![allow(shrink)] array_of_u8s);
1092+
assert_eq!(x, [[0u8, 1], [2, 3], [4, 5]]);
1093+
10491094
// Test that the source expression's value is forgotten rather than
10501095
// dropped.
10511096
#[derive(IntoBytes)]
@@ -1058,12 +1103,16 @@ mod tests {
10581103
}
10591104
#[allow(clippy::let_unit_value)]
10601105
let _: () = transmute!(PanicOnDrop(()));
1106+
#[allow(clippy::let_unit_value)]
1107+
let _: () = transmute!(#![allow(shrink)] PanicOnDrop(()));
10611108

10621109
// Test that `transmute!` is legal in a const context.
10631110
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
10641111
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
10651112
const X: [[u8; 2]; 4] = transmute!(ARRAY_OF_U8S);
10661113
assert_eq!(X, ARRAY_OF_ARRAYS);
1114+
const X_SHRINK: [[u8; 2]; 3] = transmute!(#![allow(shrink)] ARRAY_OF_U8S);
1115+
assert_eq!(X_SHRINK, [[0u8, 1], [2, 3], [4, 5]]);
10671116

10681117
// Test that `transmute!` works with `!Immutable` types.
10691118
let x: usize = transmute!(UnsafeCell::new(1usize));

tests/ui-msrv/include_value_not_from_bytes.stderr

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
44
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy<u32>`
66
|
7-
note: required by `AssertIsFromBytes`
7+
note: required by a bound in `NOT_FROM_BYTES::transmute`
88
--> tests/ui-msrv/include_value_not_from_bytes.rs:19:42
99
|
1010
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: this error originates in the macro `$crate::transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `NOT_FROM_BYTES::transmute`
15+
= note: this error originates in the macro `$crate::__transmute_inner` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-dst-not-frombytes.stderr

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ error[E0277]: the trait bound `NotZerocopy: zerocopy::FromBytes` is not satisfie
44
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
55
| ^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy`
66
|
7-
note: required by `AssertIsFromBytes`
7+
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
88
--> tests/ui-msrv/transmute-dst-not-frombytes.rs:19:41
99
|
1010
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
1111
| ^^^^^^^^^^^^^^^^^^^
12-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `DST_NOT_FROM_BYTES::transmute`
15+
= note: this error originates in the macro `$crate::__transmute_inner` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-ptr-to-usize.stderr

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@ error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
44
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoBytes` is not implemented for `*const usize`
66
|
7-
note: required by `AssertIsIntoBytes`
7+
note: required by a bound in `POINTER_VALUE::transmute`
88
--> tests/ui-msrv/transmute-ptr-to-usize.rs:20:30
99
|
1010
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
13-
14-
error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
15-
--> tests/ui-msrv/transmute-ptr-to-usize.rs:20:30
16-
|
17-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
18-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoBytes` is not implemented for `*const usize`
19-
|
20-
note: required by a bound in `AssertIsIntoBytes`
21-
--> tests/ui-msrv/transmute-ptr-to-usize.rs:20:30
22-
|
23-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
24-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `POINTER_VALUE::transmute`
2515
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-size-increase-allow-shrink.rs
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
--> tests/ui-msrv/transmute-size-increase-allow-shrink.rs:20:29
3+
|
4+
20 | const INCREASE_SIZE: AU16 = transmute!(#![allow(shrink)] 0u8);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `u8` (8 bits)
8+
= note: target type: `Transmute<u8, AU16>` (16 bits)
9+
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-msrv/transmute-src-not-intobytes.stderr

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@ error[E0277]: the trait bound `NotZerocopy<AU16>: zerocopy::IntoBytes` is not sa
44
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::IntoBytes` is not implemented for `NotZerocopy<AU16>`
66
|
7-
note: required by `AssertIsIntoBytes`
7+
note: required by a bound in `SRC_NOT_AS_BYTES::transmute`
88
--> tests/ui-msrv/transmute-src-not-intobytes.rs:19:32
99
|
1010
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
13-
14-
error[E0277]: the trait bound `NotZerocopy<AU16>: zerocopy::IntoBytes` is not satisfied
15-
--> tests/ui-msrv/transmute-src-not-intobytes.rs:19:32
16-
|
17-
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
18-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::IntoBytes` is not implemented for `NotZerocopy<AU16>`
19-
|
20-
note: required by a bound in `AssertIsIntoBytes`
21-
--> tests/ui-msrv/transmute-src-not-intobytes.rs:19:32
22-
|
23-
19 | const SRC_NOT_AS_BYTES: AU16 = transmute!(NotZerocopy(AU16(0)));
24-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
12+
| |
13+
| required by a bound in this
14+
| required by this bound in `SRC_NOT_AS_BYTES::transmute`
2515
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-nightly/include_value_not_from_bytes.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
22
--> tests/ui-nightly/include_value_not_from_bytes.rs:19:42
33
|
44
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6-
| |
7-
| the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy<u32>`
8-
| required by a bound introduced by this call
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy<u32>`
96
|
107
= note: Consider adding `#[derive(FromBytes)]` to `NotZerocopy<u32>`
118
= help: the following other types implement trait `zerocopy::FromBytes`:
@@ -18,9 +15,12 @@ error[E0277]: the trait bound `NotZerocopy<u32>: zerocopy::FromBytes` is not sat
1815
AtomicIsize
1916
AtomicU16
2017
and $N others
21-
note: required by a bound in `AssertIsFromBytes`
18+
note: required by a bound in `NOT_FROM_BYTES::transmute`
2219
--> tests/ui-nightly/include_value_not_from_bytes.rs:19:42
2320
|
2421
19 | const NOT_FROM_BYTES: NotZerocopy<u32> = include_value!("../../testdata/include_value/data");
25-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsFromBytes`
26-
= note: this error originates in the macro `$crate::transmute` which comes from the expansion of the macro `include_value` (in Nightly builds, run with -Z macro-backtrace for more info)
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
| |
24+
| required by a bound in this function
25+
| required by this bound in `transmute`
26+
= note: this error originates in the macro `$crate::__transmute_inner` which comes from the expansion of the macro `include_value` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-nightly/transmute-dst-not-frombytes.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ error[E0277]: the trait bound `NotZerocopy: zerocopy::FromBytes` is not satisfie
22
--> tests/ui-nightly/transmute-dst-not-frombytes.rs:19:41
33
|
44
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
5-
| ^^^^^^^^^^^^^^^^^^^
6-
| |
7-
| the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy`
8-
| required by a bound introduced by this call
5+
| ^^^^^^^^^^^^^^^^^^^ the trait `zerocopy::FromBytes` is not implemented for `NotZerocopy`
96
|
107
= note: Consider adding `#[derive(FromBytes)]` to `NotZerocopy`
118
= help: the following other types implement trait `zerocopy::FromBytes`:
@@ -18,9 +15,12 @@ error[E0277]: the trait bound `NotZerocopy: zerocopy::FromBytes` is not satisfie
1815
AtomicIsize
1916
AtomicU16
2017
and $N others
21-
note: required by a bound in `AssertIsFromBytes`
18+
note: required by a bound in `DST_NOT_FROM_BYTES::transmute`
2219
--> tests/ui-nightly/transmute-dst-not-frombytes.rs:19:41
2320
|
2421
19 | const DST_NOT_FROM_BYTES: NotZerocopy = transmute!(AU16(0));
25-
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsFromBytes`
26-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
22+
| ^^^^^^^^^^^^^^^^^^^
23+
| |
24+
| required by a bound in this function
25+
| required by this bound in `transmute`
26+
= note: this error originates in the macro `$crate::__transmute_inner` which comes from the expansion of the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui-nightly/transmute-ptr-to-usize.stderr

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,12 @@ error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
99
|
1010
= note: Consider adding `#[derive(IntoBytes)]` to `*const usize`
1111
= help: the trait `IntoBytes` is implemented for `usize`
12-
note: required by a bound in `AssertIsIntoBytes`
12+
note: required by a bound in `POINTER_VALUE::transmute`
1313
--> tests/ui-nightly/transmute-ptr-to-usize.rs:20:30
1414
|
1515
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
16-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
17-
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
18-
19-
error[E0277]: the trait bound `*const usize: IntoBytes` is not satisfied
20-
--> tests/ui-nightly/transmute-ptr-to-usize.rs:20:30
21-
|
22-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
23-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoBytes` is not implemented for `*const usize`
24-
|
25-
= note: Consider adding `#[derive(IntoBytes)]` to `*const usize`
26-
= help: the trait `IntoBytes` is implemented for `usize`
27-
note: required by a bound in `AssertIsIntoBytes`
28-
--> tests/ui-nightly/transmute-ptr-to-usize.rs:20:30
29-
|
30-
20 | const POINTER_VALUE: usize = transmute!(&0usize as *const usize);
31-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `AssertIsIntoBytes`
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17+
| |
18+
| required by a bound in this function
19+
| required by this bound in `transmute`
3220
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)