zerocopy
zerocopy copied to clipboard
Implement Inline Size Assertion Annotations
Implem the procedural macro inline_assert_size_eq to check the size of a type at compile time. Additionally, add the test file inline_assert_size_eq_failed.rs to ensure that the macro tirggers a compile-time error when the size is not as expected Fixes #1329
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 87.71%. Comparing base (
f1f0184) to head (905143c). Report is 470 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #1405 +/- ##
=======================================
Coverage 87.71% 87.71%
=======================================
Files 15 15
Lines 5138 5138
=======================================
Hits 4507 4507
Misses 631 631
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
Thanks for being so responsive to feedback! Just a heads up: It might be awhile before we're ready to merge this. We'll need to be absolutely certain about both the syntax and its interaction with generic types before we merge, and we are currently prioritizing the backlog of issues that are blocking the 0.8 release.
Hi @jswrenn, I'm currently learning about derive macros and am trying to figure out how to make them work with generic types.
I'm struggling with how to capture and parse attributes such as offset = 0, size = 2 * core::mem::size_of::<T>(). I'm not entirely sure what to refer to these as or how to manage them, so I've been searching for some tutorials to help me out.
From the video A Practical Introduction to Derive Macros with Attributes suggests that to parse attributes, one should use the deluxe crate and create a struct that derives from deluxe::ExtractAttributes. This struct would then declare the attributes as fields. However, I'm stumped by expressions like 2 * core::mem::size_of::<T>() because I don't know their type, making it challenging to declare them in the struct.
Am I on the right track here? Could you offer any advice or guidance?
I think we'd like to avoid taking additional dependencies for this. To do it with syn, you'd leverage its parse module, like so:
use syn::{
Attribute,
Expr,
parse::{Parse, ParseStream},
parse_quote,
Token,
punctuated::Punctuated,
};
/// Declarations the qualities of a layout.
///
/// These function both as checked assertions, and as hints to `KnownLayout`.
struct Hints {
hints: Punctuated<Hint, Token![,]>,
}
impl Parse for Hints {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
hints: input.parse_terminated(Hint::parse, Token![,])?,
})
}
}
/// A declaration about type/field layout qualities.
struct Hint {
kind: HintKind,
colon: Token![:],
expr: Expr,
}
impl Parse for Hint {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
kind: input.parse()?,
colon: input.parse()?,
expr: input.parse()?,
})
}
}
mod keywords {
syn::custom_keyword!(align);
syn::custom_keyword!(offset);
syn::custom_keyword!(size);
}
/// The layout quality a hint is about.
///
/// A hint is either about alignment, offset or size.
enum HintKind {
/// The alignment of a type or field.
Align(keywords::align),
/// The offset of a field.
Offset(keywords::offset),
/// The size of a type or field.
Size(keywords::size),
}
impl Parse for HintKind {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(keywords::align) {
input.parse().map(HintKind::Align)
} else if lookahead.peek(keywords::offset) {
input.parse().map(HintKind::Offset)
} else if lookahead.peek(keywords::size) {
input.parse().map(HintKind::Size)
} else {
Err(lookahead.error())
}
}
}
fn main() -> syn::Result<()> {
let attr: Attribute = parse_quote! {
#[hint(
align: 4,
offset: 8,
size: 0,
)]
};
if attr.path().is_ident("hint") {
let hints: Hints = attr.parse_args()?;
// do something with `hints`
}
Ok(())
}
An idea I've reflected in the above code is that I think we should call these 'hints', not assertions, because they can serve the dual roles of both being checkable at parse time, but also these can be leveraged by KnownLayout so we can derive it on more types.
Well, that's the idea at least. There are a lot of particulars to figure out here.
Then, to siphon the hints out of the AST, you'll want to leverage the visit module, like so (playground):
use syn::{
parse,
visit::{self, Visit},
DeriveInput, Field, Generics, Ident,
};
/// Visits the AST and collects layout hints.
#[derive(Debug, Default)]
struct HintCollector<'ast> {
hints: Vec<TargetedHint<'ast>>,
errors: Vec<parse::Error>,
}
/// A hint and its target (i.e., type or field).
#[derive(Debug)]
struct TargetedHint<'ast> {
target: HintTarget<'ast>,
hints: Hints,
}
/// The target of a hint.
#[derive(Copy, Clone, Debug)]
enum HintTarget<'ast> {
Type(&'ast DeriveInput),
Field(&'ast Field),
}
impl<'ast> HintCollector<'ast> {
/// Collect the `Hints` for the given target from its given attribute list.
fn visit_attributes(&mut self, target: HintTarget<'ast>, attrs: &'ast Vec<Attribute>) {
let hints = attrs.iter().filter(|attr| attr.path().is_ident("hint"));
for attr in hints {
match attr.parse_args::<Hints>() {
Ok(hints) => {
self.hints.push(TargetedHint { target, hints });
}
Err(err) => {
self.errors.push(err);
}
}
}
}
}
impl<'ast> Visit<'ast> for HintCollector<'ast> {
fn visit_derive_input(&mut self, i: &'ast DeriveInput) {
self.visit_attributes(HintTarget::Type(i), &i.attrs);
visit::visit_derive_input(self, i);
}
fn visit_field(&mut self, i: &'ast Field) {
self.visit_attributes(HintTarget::Field(i), &i.attrs);
visit::visit_field(self, i);
}
}
fn main() -> syn::Result<()> {
use syn::DeriveInput;
let input: DeriveInput = parse_quote! {
struct Foo<T, const N: usize> {
#[hint(
align: 2,
offset: 0,
size: 2,
)]
a: u16,
#[hint(
align: core::mem::align_of::<T>(),
offset: 4,
size: core::mem::size_of::<T>() * N,
)]
b: [T; N]
}
};
let mut collector = HintCollector::default();
collector.visit_derive_input(&input);
println!("{:#?}", collector);
Ok(())
}