Skip to main content

zerocopy_derive/
lib.rs

1// SPDX-License-Identifier: BSD-2-Clause OR Apache-2.0 OR MIT
2//
3// Copyright 2019 The Fuchsia Authors
4//
5// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
6// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
7// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
8// This file may not be copied, modified, or distributed except according to
9// those terms.
10
11//! Derive macros for [zerocopy]'s traits.
12//!
13//! [zerocopy]: https://docs.rs/zerocopy
14
15// Sometimes we want to use lints which were added after our MSRV.
16// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
17// this attribute, any unknown lint would cause a CI failure when testing with
18// our MSRV.
19#![allow(unknown_lints)]
20#![deny(renamed_and_removed_lints)]
21#![deny(
22    clippy::all,
23    clippy::missing_safety_doc,
24    clippy::multiple_unsafe_ops_per_block,
25    clippy::undocumented_unsafe_blocks
26)]
27// We defer to own discretion on type complexity.
28#![allow(clippy::type_complexity)]
29// Inlining format args isn't supported on our MSRV.
30#![allow(clippy::uninlined_format_args)]
31// `cargo-zerocopy` supplies this cfg for pinned-nightly tests. During UI tests,
32// `testutil::UiTestRunner` explicitly supplies it to the host-built proc macro;
33// ordinary `RUSTFLAGS` are not sufficient when Cargo receives `--target`.
34#![cfg_attr(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS, feature(proc_macro_def_site))]
35#![deny(
36    rustdoc::bare_urls,
37    rustdoc::broken_intra_doc_links,
38    rustdoc::invalid_codeblock_attributes,
39    rustdoc::invalid_html_tags,
40    rustdoc::invalid_rust_codeblocks,
41    rustdoc::missing_crate_level_docs,
42    rustdoc::private_intra_doc_links
43)]
44#![recursion_limit = "128"]
45
46macro_rules! ident {
47    (($fmt:literal $(, $arg:expr)*), $span:expr) => {
48        syn::Ident::new(&format!($fmt $(, crate::util::to_ident_str($arg))*), $span)
49    };
50}
51
52mod derive;
53mod invariant;
54#[cfg(test)]
55mod output_tests;
56mod repr;
57mod util;
58
59use syn::{DeriveInput, Error};
60
61use crate::util::*;
62
63// FIXME(https://github.com/rust-lang/rust/issues/54140): Some errors could be
64// made better if we could add multiple lines of error output like this:
65//
66// error: unsupported representation
67//   --> enum.rs:28:8
68//    |
69// 28 | #[repr(transparent)]
70//    |
71// help: required by the derive of FromBytes
72//
73// Instead, we have more verbose error messages like "unsupported representation
74// for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum"
75//
76// This will probably require Span::error
77// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
78// which is currently unstable. Revisit this once it's stable.
79
80/// Defines a derive function named `$outer` which parses its input
81/// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function.
82///
83/// Note that the separate `$outer` parameter is required - proc macro functions
84/// are currently required to live at the crate root, and so the caller must
85/// specify the name in order to avoid name collisions.
86macro_rules! derive {
87    ($(#[$attr:meta])* $trait:ident => $outer:ident => $inner:path) => {
88        $(#[$attr])*
89        #[proc_macro_derive($trait, attributes(zerocopy))]
90        pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
91            let ast = syn::parse_macro_input!(ts as DeriveInput);
92            let ctx = match Ctx::try_from_derive_input(ast) {
93                Ok(ctx) => ctx,
94                Err(e) => return e.into_compile_error().into(),
95            };
96            let ts = $inner(&ctx, Trait::$trait).into_ts();
97            // Apply generated-code lint allowances as a backstop, except
98            // around caller-authored invariant expressions.
99            let ts = if matches!(Trait::$trait, Trait::TryFromBytes) {
100                ctx.const_block([Some(ts)])
101            } else {
102                const_block([Some(ts)])
103            };
104            #[cfg(test)]
105            crate::util::testutil::check_hygiene(ts.clone());
106            ts.into()
107        }
108    };
109}
110
111trait IntoTokenStream {
112    fn into_ts(self) -> proc_macro2::TokenStream;
113}
114
115impl IntoTokenStream for proc_macro2::TokenStream {
116    fn into_ts(self) -> proc_macro2::TokenStream {
117        self
118    }
119}
120
121impl IntoTokenStream for Result<proc_macro2::TokenStream, Error> {
122    fn into_ts(self) -> proc_macro2::TokenStream {
123        match self {
124            Ok(ts) => ts,
125            Err(err) => err.to_compile_error(),
126        }
127    }
128}
129
130derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive);
131derive!(Immutable => derive_immutable => crate::derive::derive_immutable);
132derive!(#[doc(hidden)] Project => derive_project => crate::derive::project::derive);
133derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes);
134derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros);
135derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes);
136derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes);
137derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned);
138derive!(ByteHash => derive_hash => crate::derive::derive_hash);
139derive!(ByteEq => derive_eq => crate::derive::derive_eq);
140derive!(SplitAt => derive_split_at => crate::derive::derive_split_at);
141
142/// Generates a struct whose two identically-printed field types resolve to
143/// different types. This is used to test that derives preserve type identity
144/// across macro hygiene contexts.
145#[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)]
146#[doc(hidden)]
147#[proc_macro]
148pub fn __test_hygienically_mixed_into_bytes(
149    _input: proc_macro::TokenStream,
150) -> proc_macro::TokenStream {
151    use proc_macro::{Group, Ident, Span, TokenStream, TokenTree};
152
153    fn rewrite(input: TokenStream) -> TokenStream {
154        input
155            .into_iter()
156            .map(|token| match token {
157                TokenTree::Ident(ident) if ident.to_string() == "CallT" => {
158                    TokenTree::Ident(Ident::new("T", Span::call_site()))
159                }
160                TokenTree::Ident(ident) if ident.to_string() == "DefT" => {
161                    TokenTree::Ident(Ident::new("T", Span::def_site()))
162                }
163                TokenTree::Group(group) => {
164                    let mut rewritten = Group::new(group.delimiter(), rewrite(group.stream()));
165                    rewritten.set_span(group.span());
166                    TokenTree::Group(rewritten)
167                }
168                token => token,
169            })
170            .collect()
171    }
172
173    rewrite(
174        "#[derive(zerocopy_renamed::IntoBytes)] \
175         #[zerocopy(crate = \"zerocopy_renamed\")] \
176         #[repr(C)] \
177         struct IntoBytes15<DefT>(CallT, DefT);"
178            .parse()
179            .expect("test input must parse"),
180    )
181}
182
183/// Constructs an invariant whose field declaration and reference have different
184/// syntax contexts, while preserving all other caller tokens.
185#[doc(hidden)]
186#[proc_macro]
187pub fn __test_hygienically_mixed_invariant(
188    input: proc_macro::TokenStream,
189) -> proc_macro::TokenStream {
190    // Cross-compilation does not necessarily pass the nightly test cfg to
191    // host proc macros. Omit the fixture and its test together in that case.
192    #[cfg(not(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS))]
193    {
194        let _ = input;
195        proc_macro::TokenStream::new()
196    }
197    #[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)]
198    {
199        use proc_macro::{Group, Ident, Span, TokenStream, TokenTree};
200
201        fn rewrite(input: TokenStream) -> TokenStream {
202            input
203                .into_iter()
204                .map(|token| match token {
205                    TokenTree::Ident(ident) if ident.to_string() == "DefField" => {
206                        TokenTree::Ident(Ident::new("field", Span::def_site()))
207                    }
208                    TokenTree::Ident(ident) if ident.to_string() == "CallField" => {
209                        TokenTree::Ident(Ident::new("field", Span::call_site()))
210                    }
211                    TokenTree::Group(group) => {
212                        let mut rewritten = Group::new(group.delimiter(), rewrite(group.stream()));
213                        rewritten.set_span(group.span());
214                        TokenTree::Group(rewritten)
215                    }
216                    token => token,
217                })
218                .collect()
219        }
220
221        rewrite(input)
222    }
223}
224
225#[cfg_attr(not(zerocopy_unstable_linux), doc(hidden))]
226#[proc_macro_derive(most_traits, attributes(zerocopy))]
227pub fn most_traits(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
228    let ast = syn::parse_macro_input!(ts as DeriveInput);
229    let ctx = match Ctx::try_from_derive_input(ast) {
230        Ok(ctx) => ctx,
231        Err(e) => return e.into_compile_error().into(),
232    }
233    .skip_on_error();
234
235    // top-level traits for which to attempt a derive
236    let derives: [(fn(&Ctx, Trait) -> _, _); 6] = [
237        (crate::derive::known_layout::derive, Trait::KnownLayout),
238        (crate::derive::derive_immutable, Trait::Immutable),
239        (crate::derive::from_bytes::derive_from_bytes, Trait::FromBytes),
240        (crate::derive::into_bytes::derive_into_bytes, Trait::IntoBytes),
241        (crate::derive::derive_split_at, Trait::SplitAt),
242        (crate::derive::unaligned::derive_unaligned, Trait::Unaligned),
243    ];
244
245    let mut tokens = proc_macro2::TokenStream::new();
246    for (derive, t) in derives {
247        tokens.extend(derive(&ctx, t))
248    }
249    // Invariants prevent `FromBytes` from generating its usual supertrait
250    // impls, but still permit checked conversions through `TryFromBytes`.
251    if ctx.invariant_span.is_some() {
252        tokens.extend(crate::derive::try_from_bytes::derive_try_from_bytes(
253            &ctx,
254            Trait::TryFromBytes,
255        ));
256    }
257
258    // Apply generated-code lint allowances as a backstop, except around
259    // caller-authored invariant expressions.
260    let ts = ctx.const_block([Some(tokens)]);
261    #[cfg(test)]
262    crate::util::testutil::check_hygiene(ts.clone());
263    ts.into()
264}
265
266/// Deprecated: prefer [`FromZeros`] instead.
267#[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
268#[doc(hidden)]
269#[proc_macro_derive(FromZeroes)]
270pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
271    derive_from_zeros(ts)
272}
273
274/// Deprecated: prefer [`IntoBytes`] instead.
275#[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")]
276#[doc(hidden)]
277#[proc_macro_derive(AsBytes)]
278pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
279    derive_into_bytes(ts)
280}