Skip to content

Commit

Permalink
Support multiple and named fields in #[repr(C, u*)] enums
Browse files Browse the repository at this point in the history
  • Loading branch information
p-avital committed Jun 23, 2024
1 parent b898cc6 commit 70a20d9
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 56 deletions.
81 changes: 50 additions & 31 deletions stabby-macros/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,39 +120,56 @@ pub fn stabby(
let DataEnum { variants, .. } = &data;
let mut has_non_empty_fields = false;
let unit = syn::parse2(quote!(())).unwrap();
let mut report = Vec::new();
let mut report = crate::Report::r#enum(ident.to_string(), 0);
for variant in variants {
match &variant.fields {
syn::Fields::Named(f) if matches!(repr, Some(FullRepr { is_c: true, .. })) => {
todo!("stabby support for named fields is undergoing implementation...")
has_non_empty_fields = true;
let mut variant_report = crate::Report::r#struct(variant.ident.to_string(), 0);
let mut variant_layout = quote!(());
for f in &f.named {
let ty = &f.ty;
variant_layout = quote!(#st::FieldPair<#variant_layout, #ty>);
variant_report.add_field(f.ident.as_ref().unwrap().to_string(), ty);
}
variant_layout = quote!(#st::Struct<#variant_layout>);
layout = quote!(#st::Union<#layout, core::mem::ManuallyDrop<#variant_layout>>);
report.add_field(variant.ident.to_string(), variant_report);
}
syn::Fields::Named(_) => {
panic!("stabby only supports named fields in #[repr(C, uX)] enums");
panic!("stabby only supports named fields in #[repr(C, u*)] enums");
}
syn::Fields::Unnamed(f) => {
if f.unnamed.len() != 1 && matches!(repr, Some(FullRepr { is_c: true, .. })) {
todo!(
"stabby support for multiple fields per enum is restricted to named fields"
)
has_non_empty_fields = true;
let mut variant_report = crate::Report::r#struct(variant.ident.to_string(), 0);
let mut variant_layout = quote!(());
for (n, f) in f.unnamed.iter().enumerate() {
let ty = &f.ty;
variant_layout = quote!(#st::FieldPair<#variant_layout, #ty>);
variant_report.add_field(n.to_string(), ty);
}
variant_layout = quote!(#st::Struct<#variant_layout>);
layout = quote!(#st::Union<#layout, core::mem::ManuallyDrop<#variant_layout>>);
report.add_field(variant.ident.to_string(), variant_report);
} else {
assert_eq!(
f.unnamed.len(),
1,
"stabby only supports multiple fields per enum variant in #[repr(C, uX)] enums"
"stabby only supports multiple fields per enum variant in #[repr(C, u*)] enums"
);
has_non_empty_fields = true;
let f = f.unnamed.first().unwrap();
let ty = &f.ty;
layout = quote!(#st::Union<#layout, core::mem::ManuallyDrop<#ty>>);
report.push((variant.ident.to_string(), ty));
report.add_field(variant.ident.to_string(), ty);
}
}
syn::Fields::Unit => {
report.push((variant.ident.to_string(), &unit));
report.add_field(variant.ident.to_string(), &unit);
}
}
}
let report = crate::report(&report);
let reprstr = match repr
.as_ref()
.and_then(|r| if r.is_c { Some(Repr::C) } else { r.repr })
Expand All @@ -166,12 +183,12 @@ pub fn stabby(
&vis,
&ident,
&generics,
data,
data.clone(),
report,
repr.is_none(),
);
}
Some(Repr::C) => "u8",
Some(Repr::C) => "u8", // TODO: Remove support for `#[repr(C)]` alone on the next API-breaking release
Some(Repr::U8) => "u8",
Some(Repr::U16) => "u16",
Some(Repr::U32) => "u32",
Expand All @@ -190,8 +207,14 @@ pub fn stabby(
quote!(#[repr(#reprid)])
};
layout = quote!(#st::Tuple<#reprid, #layout>);
let sident = format!("{ident}");
let (report, report_bounds) = report;
report.tyty = quote!(#st::report::TyTy::Enum(#st::str::Str::new(#reprstr)));
let report_bounds = report.bounds();
let size_bug = format!(
"{ident}'s size was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue"
);
let align_bug = format!(
"{ident}'s align was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue"
);
quote! {
#(#new_attrs)*
#reprattr
Expand All @@ -207,14 +230,16 @@ pub fn stabby(
type Align = <#layout as #st::IStable>::Align;
type HasExactlyOneNiche = #st::B0;
type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections;
const REPORT: &'static #st::report::TypeReport = & #st::report::TypeReport {
name: #st::str::Str::new(#sident),
module: #st::str::Str::new(core::module_path!()),
fields: unsafe {#st::StableLike::new(#report)},
version: 0,
tyty: #st::report::TyTy::Enum(#st::str::Str::new(#reprstr)),
const REPORT: &'static #st::report::TypeReport = & #report;
const ID: u64 ={
if core::mem::size_of::<Self>() != <<Self as #st::IStable>::Size as #st::Unsigned>::USIZE {
panic!(#size_bug)
}
if core::mem::align_of::<Self>() != <<Self as #st::IStable>::Align as #st::Unsigned>::USIZE {
panic!(#align_bug)
}
#st::report::gen_id(Self::REPORT)
};
const ID: u64 = #st::report::gen_id(Self::REPORT);
}
}
}
Expand Down Expand Up @@ -316,7 +341,7 @@ pub fn repr_stabby(
ident: &Ident,
generics: &Generics,
data: DataEnum,
report: (TokenStream, TokenStream),
mut report: crate::Report,
check: bool,
) -> TokenStream {
let st = crate::tl_mod();
Expand Down Expand Up @@ -428,8 +453,8 @@ pub fn repr_stabby(
let bounds2 = generics.where_clause.as_ref().map(|c| &c.predicates);
let bounds = quote!(#bounds #bounds2);

let sident = format!("{ident}");
let (report, report_bounds) = report;
report.tyty = quote!(#st::report::TyTy::Enum(#st::str::Str::new("stabby")));
let report_bounds = report.bounds();
let enum_as_struct = quote! {
#(#attrs)*
#vis struct #ident #generics (#result) where #report_bounds #bounds;
Expand Down Expand Up @@ -491,13 +516,7 @@ pub fn repr_stabby(
type Align = <#layout as #st::IStable>::Align;
type HasExactlyOneNiche = #st::B0;
type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections;
const REPORT: &'static #st::report::TypeReport = & #st::report::TypeReport {
name: #st::str::Str::new(#sident),
module: #st::str::Str::new(core::module_path!()),
fields: unsafe {#st::StableLike::new(#report)},
version: 0,
tyty: #st::report::TyTy::Enum(#st::str::Str::new("stabby")),
};
const REPORT: &'static #st::report::TypeReport = & #report;
const ID: u64 = #st::report::gen_id(Self::REPORT);
}
#[automatically_derived]
Expand Down
108 changes: 107 additions & 1 deletion stabby-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::collections::HashSet;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use quote::{quote, ToTokens};
use syn::{parse::Parser, DeriveInput, TypeParamBound};

#[allow(dead_code)]
Expand Down Expand Up @@ -253,6 +253,112 @@ pub fn gen_closures_impl(_: TokenStream) -> TokenStream {
gen_closures::gen_closures().into()
}

enum Type<'a> {
Syn(&'a syn::Type),
Report(Report<'a>),
}
impl<'a> From<&'a syn::Type> for Type<'a> {
fn from(value: &'a syn::Type) -> Self {
Self::Syn(value)
}
}
impl<'a> From<Report<'a>> for Type<'a> {
fn from(value: Report<'a>) -> Self {
Self::Report(value)
}
}
pub(crate) struct Report<'a> {
name: String,
fields: Vec<(String, Type<'a>)>,
version: u32,
pub tyty: proc_macro2::TokenStream,
}
impl<'a> Report<'a> {
pub fn r#struct(name: impl Into<String>, version: u32) -> Self {
let st = crate::tl_mod();
Self {
name: name.into(),
fields: Vec::new(),
version,
tyty: quote!(#st::report::TyTy::Struct),
}
}
pub fn r#enum(name: impl Into<String>, version: u32) -> Self {
let st = crate::tl_mod();
Self {
name: name.into(),
fields: Vec::new(),
version,
tyty: quote!(#st::report::TyTy::Struct),
}
}
pub fn add_field(&mut self, name: String, ty: impl Into<Type<'a>>) {
self.fields.push((name, ty.into()));
}
fn __bounds(
&self,
bounded_types: &mut HashSet<&'a syn::Type>,
mut report_bounds: proc_macro2::TokenStream,
st: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
for (_, ty) in self.fields.iter() {
match ty {
Type::Syn(ty) => {
if bounded_types.insert(*ty) {
report_bounds = quote!(#ty: #st::IStable, #report_bounds);
}
}
Type::Report(report) => {
report_bounds = report.__bounds(bounded_types, report_bounds, st)
}
}
}
report_bounds
}
pub fn bounds(&self) -> proc_macro2::TokenStream {
let st = crate::tl_mod();
let mut bounded_types = HashSet::new();
self.__bounds(&mut bounded_types, quote!(), &st)
}
}
impl ToTokens for Report<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let st = crate::tl_mod();
let mut fields = quote!(None);
for (name, ty) in &self.fields {
fields = match ty {
Type::Syn(ty) => quote! {
Some(& #st::report::FieldReport {
name: #st::str::Str::new(#name),
ty: <#ty as #st::IStable>::REPORT,
next_field: #st::StableLike::new(#fields)
})
},
Type::Report(re) => quote! {
Some(& #st::report::FieldReport {
name: #st::str::Str::new(#name),
ty: &#re,
next_field: #st::StableLike::new(#fields)
})
},
}
}
let Self {
name,
version,
tyty,
..
} = self;
tokens.extend(quote!(#st::report::TypeReport {
name: #st::str::Str::new(#name),
module: #st::str::Str::new(core::module_path!()),
fields: unsafe{#st::StableLike::new(#fields)},
version: #version,
tyty: #tyty,
}));
}
}

pub(crate) fn report(
fields: &[(String, &syn::Type)],
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
Expand Down
43 changes: 19 additions & 24 deletions stabby-macros/src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn stabby(
let where_clause = &generics.where_clause;
let clauses = where_clause.as_ref().map(|w| &w.predicates);
let mut layout = None;
let mut report = Vec::new();
let mut report = crate::Report::r#struct(ident.to_string(), 0);
let struct_code = match &fields {
syn::Fields::Named(fields) => {
let fields = &fields.named;
Expand All @@ -48,7 +48,7 @@ pub fn stabby(
|| quote!(#ty),
|layout| quote!(#st::FieldPair<#layout, #ty>),
));
report.push((field.ident.as_ref().unwrap().to_string(), ty));
report.add_field(field.ident.as_ref().unwrap().to_string(), ty);
}
quote! {
#(#attrs)*
Expand All @@ -66,7 +66,7 @@ pub fn stabby(
|| quote!(#ty),
|layout| quote!(#st::FieldPair<#layout, #ty>),
));
report.push((i.to_string(), ty));
report.add_field(i.to_string(), ty);
}
quote! {
#(#attrs)*
Expand All @@ -84,32 +84,25 @@ pub fn stabby(
};
let layout = layout.map_or_else(|| quote!(()), |layout| quote!(#st::Struct<#layout>));
let opt_id = quote::format_ident!("OptimizedLayoutFor{ident}");
let size_bug = format!(
"{ident}'s size was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue"
);
let align_bug = format!(
"{ident}'s align was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please file an issue"
);
let assertion = opt.then(|| {
let sub_optimal_message = format!(
"{ident}'s layout is sub-optimal, reorder fields or use `#[stabby::stabby(no_opt)]`"
);
let size_bug = format!(
"{ident}'s size was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please fill an issue"
);
let align_bug = format!(
"{ident}'s align was mis-evaluated by stabby, this is a definitely a bug and may cause UB, please fill an issue"
);
quote! {
const _: () = {
if !<#ident>::has_optimal_layout() {
panic!(#sub_optimal_message)
}
if core::mem::size_of::<#ident>() != <<#ident as #st::IStable>::Size as #st::Unsigned>::USIZE {
panic!(#size_bug)
}
if core::mem::align_of::<#ident>() != <<#ident as #st::IStable>::Align as #st::Unsigned>::USIZE {
panic!(#align_bug)
}
};
}
});
let (report, report_bounds) = crate::report(&report);
let sident = format!("{ident}");
let report_bounds = report.bounds();
let optdoc = format!("Returns true if the layout for [`{ident}`] is smaller or equal to that Rust would have generated for it.");
quote! {
#struct_code
Expand All @@ -122,14 +115,16 @@ pub fn stabby(
type Align = <#layout as #st::IStable>::Align;
type HasExactlyOneNiche = <#layout as #st::IStable>::HasExactlyOneNiche;
type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections;
const REPORT: &'static #st::report::TypeReport = & #st::report::TypeReport {
name: #st::str::Str::new(#sident),
module: #st::str::Str::new(core::module_path!()),
fields: unsafe{#st::StableLike::new(#report)},
version: 0,
tyty: #st::report::TyTy::Struct,
const REPORT: &'static #st::report::TypeReport = &#report;
const ID: u64 = {
if core::mem::size_of::<Self>() != <<Self as #st::IStable>::Size as #st::Unsigned>::USIZE {
panic!(#size_bug)
}
if core::mem::align_of::<Self>() != <<Self as #st::IStable>::Align as #st::Unsigned>::USIZE {
panic!(#align_bug)
}
#st::report::gen_id(Self::REPORT)
};
const ID: u64 = #st::report::gen_id(Self::REPORT);
}
#[allow(dead_code, missing_docs)]
struct #opt_id #generics #where_clause #fields #semi_token
Expand Down
10 changes: 10 additions & 0 deletions stabby/tests/layouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ pub enum MultiFieldsC {
D(u8),
E,
}
#[stabby::stabby]
#[repr(C, u8)]
#[allow(dead_code)]
pub enum MultipleFieldsPerVariant {
A(NonZeroU16, u8),
B,
C { c1: u8, c2: u16 },
D(u8),
E,
}

#[stabby::stabby]
#[repr(stabby)]
Expand Down

0 comments on commit 70a20d9

Please sign in to comment.