blob: c650b59eaa82c0417ed76a8628281af41c92a5bd [file] [edit]
// 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)
}