| // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT | |
| // | |
| // Copyright 2019 The Fuchsia Authors | |
| // | |
| // Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 | |
| // <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT | |
| // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. | |
| // This file may not be copied, modified, or distributed except according to | |
| // those terms. | |
| //! Derive macros for [zerocopy]'s traits. | |
| //! | |
| //! [zerocopy]: https://docs.rs/zerocopy | |
| // Sometimes we want to use lints which were added after our MSRV. | |
| // `unknown_lints` is `warn` by default and we deny warnings in CI, so without | |
| // this attribute, any unknown lint would cause a CI failure when testing with | |
| // our MSRV. | |
| #![allow(unknown_lints)] | |
| #![deny(renamed_and_removed_lints)] | |
| #![deny( | |
| clippy::all, | |
| clippy::missing_safety_doc, | |
| clippy::multiple_unsafe_ops_per_block, | |
| clippy::undocumented_unsafe_blocks | |
| )] | |
| // We defer to own discretion on type complexity. | |
| #![allow(clippy::type_complexity)] | |
| // Inlining format args isn't supported on our MSRV. | |
| #![allow(clippy::uninlined_format_args)] | |
| // `cargo-zerocopy` supplies this cfg for pinned-nightly tests. During UI tests, | |
| // `testutil::UiTestRunner` explicitly supplies it to the host-built proc macro; | |
| // ordinary `RUSTFLAGS` are not sufficient when Cargo receives `--target`. | |
| #![cfg_attr(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS, feature(proc_macro_def_site))] | |
| #![deny( | |
| rustdoc::bare_urls, | |
| rustdoc::broken_intra_doc_links, | |
| rustdoc::invalid_codeblock_attributes, | |
| rustdoc::invalid_html_tags, | |
| rustdoc::invalid_rust_codeblocks, | |
| rustdoc::missing_crate_level_docs, | |
| rustdoc::private_intra_doc_links | |
| )] | |
| #![recursion_limit = "128"] | |
| macro_rules! ident { | |
| (($fmt:literal $(, $arg:expr)*), $span:expr) => { | |
| syn::Ident::new(&format!($fmt $(, crate::util::to_ident_str($arg))*), $span) | |
| }; | |
| } | |
| mod derive; | |
| mod invariant; | |
| #[cfg(test)] | |
| mod output_tests; | |
| mod repr; | |
| mod util; | |
| use syn::{DeriveInput, Error}; | |
| use crate::util::*; | |
| // FIXME(https://github.com/rust-lang/rust/issues/54140): Some errors could be | |
| // made better if we could add multiple lines of error output like this: | |
| // | |
| // error: unsupported representation | |
| // --> enum.rs:28:8 | |
| // | | |
| // 28 | #[repr(transparent)] | |
| // | | |
| // help: required by the derive of FromBytes | |
| // | |
| // Instead, we have more verbose error messages like "unsupported representation | |
| // for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum" | |
| // | |
| // This will probably require Span::error | |
| // (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error), | |
| // which is currently unstable. Revisit this once it's stable. | |
| /// Defines a derive function named `$outer` which parses its input | |
| /// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function. | |
| /// | |
| /// Note that the separate `$outer` parameter is required - proc macro functions | |
| /// are currently required to live at the crate root, and so the caller must | |
| /// specify the name in order to avoid name collisions. | |
| macro_rules! derive { | |
| ($(#[$attr:meta])* $trait:ident => $outer:ident => $inner:path) => { | |
| $(#[$attr])* | |
| #[proc_macro_derive($trait, attributes(zerocopy))] | |
| pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { | |
| let ast = syn::parse_macro_input!(ts as DeriveInput); | |
| let ctx = match Ctx::try_from_derive_input(ast) { | |
| Ok(ctx) => ctx, | |
| Err(e) => return e.into_compile_error().into(), | |
| }; | |
| let ts = $inner(&ctx, Trait::$trait).into_ts(); | |
| // Apply generated-code lint allowances as a backstop, except | |
| // around caller-authored invariant expressions. | |
| let ts = if matches!(Trait::$trait, Trait::TryFromBytes) { | |
| ctx.const_block([Some(ts)]) | |
| } else { | |
| const_block([Some(ts)]) | |
| }; | |
| #[cfg(test)] | |
| crate::util::testutil::check_hygiene(ts.clone()); | |
| ts.into() | |
| } | |
| }; | |
| } | |
| trait IntoTokenStream { | |
| fn into_ts(self) -> proc_macro2::TokenStream; | |
| } | |
| impl IntoTokenStream for proc_macro2::TokenStream { | |
| fn into_ts(self) -> proc_macro2::TokenStream { | |
| self | |
| } | |
| } | |
| impl IntoTokenStream for Result<proc_macro2::TokenStream, Error> { | |
| fn into_ts(self) -> proc_macro2::TokenStream { | |
| match self { | |
| Ok(ts) => ts, | |
| Err(err) => err.to_compile_error(), | |
| } | |
| } | |
| } | |
| derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive); | |
| derive!(Immutable => derive_immutable => crate::derive::derive_immutable); | |
| derive!(#[doc(hidden)] Project => derive_project => crate::derive::project::derive); | |
| derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes); | |
| derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros); | |
| derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes); | |
| derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes); | |
| derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned); | |
| derive!(ByteHash => derive_hash => crate::derive::derive_hash); | |
| derive!(ByteEq => derive_eq => crate::derive::derive_eq); | |
| derive!(SplitAt => derive_split_at => crate::derive::derive_split_at); | |
| /// Generates a struct whose two identically-printed field types resolve to | |
| /// different types. This is used to test that derives preserve type identity | |
| /// across macro hygiene contexts. | |
| #[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)] | |
| #[doc(hidden)] | |
| #[proc_macro] | |
| pub fn __test_hygienically_mixed_into_bytes( | |
| _input: proc_macro::TokenStream, | |
| ) -> proc_macro::TokenStream { | |
| use proc_macro::{Group, Ident, Span, TokenStream, TokenTree}; | |
| fn rewrite(input: TokenStream) -> TokenStream { | |
| input | |
| .into_iter() | |
| .map(|token| match token { | |
| TokenTree::Ident(ident) if ident.to_string() == "CallT" => { | |
| TokenTree::Ident(Ident::new("T", Span::call_site())) | |
| } | |
| TokenTree::Ident(ident) if ident.to_string() == "DefT" => { | |
| TokenTree::Ident(Ident::new("T", Span::def_site())) | |
| } | |
| TokenTree::Group(group) => { | |
| let mut rewritten = Group::new(group.delimiter(), rewrite(group.stream())); | |
| rewritten.set_span(group.span()); | |
| TokenTree::Group(rewritten) | |
| } | |
| token => token, | |
| }) | |
| .collect() | |
| } | |
| rewrite( | |
| "#[derive(zerocopy_renamed::IntoBytes)] \ | |
| #[zerocopy(crate = \"zerocopy_renamed\")] \ | |
| #[repr(C)] \ | |
| struct IntoBytes15<DefT>(CallT, DefT);" | |
| .parse() | |
| .expect("test input must parse"), | |
| ) | |
| } | |
| /// Constructs an invariant whose field declaration and reference have different | |
| /// syntax contexts, while preserving all other caller tokens. | |
| #[doc(hidden)] | |
| #[proc_macro] | |
| pub fn __test_hygienically_mixed_invariant( | |
| input: proc_macro::TokenStream, | |
| ) -> proc_macro::TokenStream { | |
| // Cross-compilation does not necessarily pass the nightly test cfg to | |
| // host proc macros. Omit the fixture and its test together in that case. | |
| #[cfg(not(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS))] | |
| { | |
| let _ = input; | |
| proc_macro::TokenStream::new() | |
| } | |
| #[cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)] | |
| { | |
| use proc_macro::{Group, Ident, Span, TokenStream, TokenTree}; | |
| fn rewrite(input: TokenStream) -> TokenStream { | |
| input | |
| .into_iter() | |
| .map(|token| match token { | |
| TokenTree::Ident(ident) if ident.to_string() == "DefField" => { | |
| TokenTree::Ident(Ident::new("field", Span::def_site())) | |
| } | |
| TokenTree::Ident(ident) if ident.to_string() == "CallField" => { | |
| TokenTree::Ident(Ident::new("field", Span::call_site())) | |
| } | |
| TokenTree::Group(group) => { | |
| let mut rewritten = Group::new(group.delimiter(), rewrite(group.stream())); | |
| rewritten.set_span(group.span()); | |
| TokenTree::Group(rewritten) | |
| } | |
| token => token, | |
| }) | |
| .collect() | |
| } | |
| rewrite(input) | |
| } | |
| } | |
| #[cfg_attr(not(zerocopy_unstable_linux), doc(hidden))] | |
| #[proc_macro_derive(most_traits, attributes(zerocopy))] | |
| pub fn most_traits(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { | |
| let ast = syn::parse_macro_input!(ts as DeriveInput); | |
| let ctx = match Ctx::try_from_derive_input(ast) { | |
| Ok(ctx) => ctx, | |
| Err(e) => return e.into_compile_error().into(), | |
| } | |
| .skip_on_error(); | |
| // top-level traits for which to attempt a derive | |
| let derives: [(fn(&Ctx, Trait) -> _, _); 6] = [ | |
| (crate::derive::known_layout::derive, Trait::KnownLayout), | |
| (crate::derive::derive_immutable, Trait::Immutable), | |
| (crate::derive::from_bytes::derive_from_bytes, Trait::FromBytes), | |
| (crate::derive::into_bytes::derive_into_bytes, Trait::IntoBytes), | |
| (crate::derive::derive_split_at, Trait::SplitAt), | |
| (crate::derive::unaligned::derive_unaligned, Trait::Unaligned), | |
| ]; | |
| let mut tokens = proc_macro2::TokenStream::new(); | |
| for (derive, t) in derives { | |
| tokens.extend(derive(&ctx, t)) | |
| } | |
| // Invariants prevent `FromBytes` from generating its usual supertrait | |
| // impls, but still permit checked conversions through `TryFromBytes`. | |
| if ctx.invariant_span.is_some() { | |
| tokens.extend(crate::derive::try_from_bytes::derive_try_from_bytes( | |
| &ctx, | |
| Trait::TryFromBytes, | |
| )); | |
| } | |
| // Apply generated-code lint allowances as a backstop, except around | |
| // caller-authored invariant expressions. | |
| let ts = ctx.const_block([Some(tokens)]); | |
| #[cfg(test)] | |
| crate::util::testutil::check_hygiene(ts.clone()); | |
| ts.into() | |
| } | |
| /// Deprecated: prefer [`FromZeros`] instead. | |
| #[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")] | |
| #[doc(hidden)] | |
| #[proc_macro_derive(FromZeroes)] | |
| pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { | |
| derive_from_zeros(ts) | |
| } | |
| /// Deprecated: prefer [`IntoBytes`] instead. | |
| #[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")] | |
| #[doc(hidden)] | |
| #[proc_macro_derive(AsBytes)] | |
| pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { | |
| derive_into_bytes(ts) | |
| } |