Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dispatch-bundle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
packit = []

[dependencies]
macros = { package = "embedded-command-macros", path = "../macros" }
macros = { package = "embedded-command-macros", version = "0.4.0" }

[dev-dependencies]
serac = { path = "../serac" }
8 changes: 4 additions & 4 deletions dispatch-bundle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,17 @@ mod tests {

#[test]
fn cookie_cutter() {
#[derive(vanilla::SerializeIter, vanilla::SerializeBuf)]
#[derive(vanilla::SerializeIter, vanilla::Size, serac::SerializeBuf)]
struct A {
val: u8,
}

#[derive(vanilla::SerializeIter, vanilla::SerializeBuf)]
#[derive(vanilla::SerializeIter, vanilla::Size, serac::SerializeBuf)]
struct B {
val: u16,
}

#[derive(vanilla::SerializeIter, vanilla::SerializeBuf)]
#[derive(vanilla::SerializeIter, vanilla::Size, serac::SerializeBuf)]
struct C {
val: u8,
other: A,
Expand All @@ -118,7 +118,7 @@ mod tests {
const TEN: u8 = 10;

#[bundle(Foo)]
#[derive(vanilla::SerializeIter, vanilla::SerializeBuf)]
#[derive(vanilla::SerializeIter, vanilla::Size, serac::SerializeBuf)]
#[repr(u8)]
enum MyBundle {
A,
Expand Down
4 changes: 2 additions & 2 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "embedded-command-macros"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
description = "Macros for the embedded command crate family."
license = "CC-BY-NC-SA-4.0"
Expand All @@ -16,4 +16,4 @@ packit = []
Inflector = "0.11.4"
proc-macro2 = "1.0.78"
quote = "1.0.35"
syn = { version = "2.0.50", features = ["full"] }
syn = { version = "2.0.50", features = ["full", "extra-traits"] }
28 changes: 25 additions & 3 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,36 @@ pub fn impl_serialize_iter_vanilla(item: TokenStream) -> TokenStream {
serac::vanilla::serialize_iter(item)
}

/// Generates the implementation block for conforming to `SerializeBuf` of the "vanilla" flavor.
/// Generates the implementation block for conforming to `Size` of the "vanilla" flavor.
///
/// # Note
///
/// Requires `serac` to be in scope with that name.
#[proc_macro_derive(Size)]
pub fn impl_size_vanilla(item: TokenStream) -> TokenStream {
serac::vanilla::impl_size(item)
}

/// Generates the implementation block for conforming to `SerializeBuf`.
///
/// As of now, generic types *cannot* implement `SerializeBuf` on stable.
///
/// # Note
///
/// Requires `serac` to be in scope with that name.
#[proc_macro_derive(SerializeBuf)]
pub fn impl_serialize_buf_vanilla(item: TokenStream) -> TokenStream {
serac::vanilla::impl_serialize_buf(item)
pub fn impl_serialize_buf(item: TokenStream) -> TokenStream {
serac::impl_serialize_buf(item)
}

/// Generates the implementation block for conforming to `SerializeBuf` for a type
/// alias. This is used for implementing `SerializeBuf` for concretely specified
/// generic types.
///
/// # Note
///
/// Requires `serac` to be in scope with that name.
#[proc_macro_attribute]
pub fn impl_serialize_buf_alias(attrs: TokenStream, item: TokenStream) -> TokenStream {
serac::impl_serialize_buf_alias(attrs, item)
}
53 changes: 53 additions & 0 deletions macros/src/serac.rs
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
pub(crate) mod vanilla;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{DeriveInput, Generics, Ident, ItemType, Path};

#[derive(Clone)]
struct BodyInfo {
ident: Ident,
generics: Generics,
path: Path,
}

pub fn impl_serialize_buf(item: TokenStream) -> TokenStream {
let item: DeriveInput = syn::parse2(item.into()).unwrap();

if !item.generics.params.is_empty() {
panic!("SerializeBuf is incompatible with generic types. You may still use SerializeIter.");
}

let info = BodyInfo {
ident: item.ident,
generics: item.generics,
path: syn::parse2(quote! { serac }).unwrap(),
};

let path = info.path;
let ident = info.ident;

quote! {
unsafe impl #path::SerializeBuf<{ <#ident as #path::Size>::SIZE }> for #ident {}
}
.into()
}

pub fn impl_serialize_buf_alias(attrs: TokenStream, item: TokenStream) -> TokenStream {
let item = item.into();
let attrs: TokenStream2 = attrs.into();

let original = quote! { #attrs #item };

let item: ItemType = syn::parse2(item).expect("a");

let path = syn::parse2::<Path>(quote! { serac }).expect("b");
let ident = item.ident;

quote! {
#original

unsafe impl #path::SerializeBuf<{ <#ident as #path::Size>::SIZE }> for #ident {}
}
.into()
}
119 changes: 80 additions & 39 deletions macros/src/serac/vanilla.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
use std::collections::HashSet;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, Index, Path, Type,
Variant,
};

#[derive(Clone)]
struct BodyInfo {
ident: Ident,
generics: Generics,
path: Path,
}
use syn::{Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Index, Type, Variant};

use crate::serac::BodyInfo;

fn get_repr<'a>(mut attrs: impl Iterator<Item = &'a Attribute>) -> Type {
attrs
Expand Down Expand Up @@ -118,6 +112,19 @@ fn serialize_struct(s: DataStruct, info: &BodyInfo) -> TokenStream2 {
}
};

let (.., types) = size_of_struct(s, info);

let where_clause = {
let constraints = types.iter().map(|ty| {
quote! { #ty: #path::SerializeIter }
});

match where_clause {
Some(w) => quote! { #w #(#constraints,)* },
None => quote! { where #(#constraints,)* },
}
};

quote! {
impl #impl_generics #path::SerializeIter for #implementer #ty_generics #where_clause {
fn serialize_iter<'a>(&self, dst: impl IntoIterator<Item = &'a mut <#path::encoding::vanilla::Vanilla as #path::encoding::Encoding>::Word>) -> Result<usize, #path::error::EndOfInput>
Expand All @@ -137,15 +144,18 @@ fn serialize_struct(s: DataStruct, info: &BodyInfo) -> TokenStream2 {
}
}

fn size_of_struct(s: DataStruct, info: &BodyInfo) -> TokenStream2 {
let types: Vec<_> = s.fields.iter().map(|field| &field.ty).collect();
fn size_of_struct(s: DataStruct, info: &BodyInfo) -> (TokenStream2, HashSet<Type>) {
let types: Vec<_> = s.fields.iter().map(|field| field.ty.clone()).collect();
let path = &info.path;

if types.is_empty() {
quote! { 0 }
} else {
quote! { #( <#types as #path::Size>::SIZE )+* }
}
(
if types.is_empty() {
quote! { 0 }
} else {
quote! { #( <#types as #path::Size>::SIZE )+* }
},
HashSet::from_iter(types),
)
}

fn serialize_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> TokenStream2 {
Expand Down Expand Up @@ -260,6 +270,19 @@ fn serialize_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> TokenStream2 {
})
.collect();

let (.., types) = size_of_enum(e, info, repr.clone());

let where_clause = {
let constraints = types.iter().map(|ty| {
quote! { #ty: #path::SerializeIter }
});

match where_clause {
Some(w) => quote! { #w #(#constraints,)* },
None => quote! { where #(#constraints,)* },
}
};

quote! {
impl #impl_generics #path::SerializeIter for #implementer #ty_generics #where_clause {
fn serialize_iter<'a>(&self, dst: impl IntoIterator<Item = &'a mut <#path::encoding::vanilla::Vanilla as #path::encoding::Encoding>::Word>) -> Result<usize, #path::error::EndOfInput>
Expand Down Expand Up @@ -303,33 +326,44 @@ fn serialize_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> TokenStream2 {
}
}

fn size_of_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> TokenStream2 {
fn size_of_enum(e: DataEnum, info: &BodyInfo, repr: Type) -> (TokenStream2, HashSet<Type>) {
let mut types = HashSet::new();

let path = &info.path;
let sizes: Vec<_> = e
.variants
.iter()
.filter_map(|variant| {
if !variant.fields.is_empty() {
let types: Vec<_> = variant.fields.iter().map(|field| &field.ty).collect();
let variant_types: Vec<_> = variant
.fields
.iter()
.map(|field| field.ty.clone())
.collect();

Some(quote! { #(<#types as #path::Size>::SIZE)+* })
types.extend(variant_types.iter().cloned());

Some(quote! { #(<#variant_types as #path::Size>::SIZE)+* })
} else {
None
}
})
.collect();

quote! {{
let mut max = 0;
(
quote! {{
let mut max = 0;

#(
if #sizes > max {
max = #sizes;
}
)*
#(
if #sizes > max {
max = #sizes;
}
)*

max + <#repr as #path::Size>::SIZE
}}
max + <#repr as #path::Size>::SIZE
}},
types,
)
}

pub fn serialize_iter(item: TokenStream) -> TokenStream {
Expand All @@ -350,34 +384,41 @@ pub fn serialize_iter(item: TokenStream) -> TokenStream {
implementation.into()
}

pub fn impl_serialize_buf(item: TokenStream) -> TokenStream {
pub fn impl_size(item: TokenStream) -> TokenStream {
let item: DeriveInput = syn::parse2(item.into()).unwrap();

if !item.generics.params.is_empty() {
panic!("SerializeBuf is incompatible with generic types. You may still use SerializeIter.");
}

let info = BodyInfo {
ident: item.ident,
generics: item.generics,
path: syn::parse2(quote! { serac }).unwrap(),
};

let size = match item.data {
let (size, types) = match item.data {
Data::Struct(s) => size_of_struct(s, &info),
Data::Enum(e) => size_of_enum(e, &info, get_repr(item.attrs.iter())),
_ => panic!("Vanilla serializer is only implemented for structs and enums."),
};

let (impl_generics, ty_generics, where_clause) = info.generics.split_for_impl();

let path = info.path;
let ident = info.ident;

let where_clause = {
let constraints = types.iter().map(|ty| {
quote! { #ty: #path::Size }
});

match where_clause {
Some(w) => quote! { #w #(#constraints,)* },
None => quote! { where #(#constraints,)* },
}
};

quote! {
unsafe impl #path::Size for #ident {
unsafe impl #impl_generics #path::Size for #ident #ty_generics #where_clause {
const SIZE: usize = #size;
}

impl #path::SerializeBuf<{ <#ident as #path::Size>::SIZE }> for #ident {}
}
.into()
}
Loading