Skip to content

Commit

Permalink
feat: Handle explicit repr on type definition
Browse files Browse the repository at this point in the history
  • Loading branch information
GrayJack committed Jan 17, 2025
1 parent f678177 commit 8adeaae
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 5 deletions.
18 changes: 18 additions & 0 deletions bitflags-attr-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,24 @@ mod typed;
/// bits, without generating additional constants for them. It helps compatibility when the external
/// source may start setting additional bits at any time.
///
/// ## Type representation
///
/// By default, the generated flag type will be `#[repr(transparent)]`, but you can explicit it on
/// the definition as long is one of the supported ones (`C`, `Rust` and `transparent`):
///
/// ```rust
/// # use bitflag_attr::bitflag;
///
/// #[repr(C)]
/// #[bitflag(u8)]
/// #[derive(Clone, Copy)]
/// enum Flags {
/// A = 1,
/// B = 1 << 1,
/// C = 1 << 2,
/// }
/// ```
///
/// ## Generated trait implementations
///
/// This macro generates some trait implementations: [`ops:Not`], [`ops:BitAnd`],
Expand Down
129 changes: 124 additions & 5 deletions bitflags-attr-macros/src/typed.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use syn::{
parse::Parse, spanned::Spanned, Attribute, Error, Expr, Ident, ItemConst, ItemEnum, LitStr,
Meta, MetaNameValue, Path, Visibility,
parse::Parse, spanned::Spanned, token::Paren, Attribute, Error, Expr, Ident, ItemConst,
ItemEnum, LitStr, Meta, MetaNameValue, Path, Visibility,
};

use proc_macro2::TokenStream;
Expand All @@ -12,6 +12,7 @@ pub struct Bitflag {
attrs: Vec<Attribute>,
name: Ident,
inner_ty: Path,
repr_attr: Option<ReprAttr>,
derived_traits: Vec<Ident>,
impl_debug: bool,
impl_serialize: bool,
Expand All @@ -33,7 +34,7 @@ impl Bitflag {
let og_attrs = item
.attrs
.iter()
.filter(|att| !att.path().is_ident("extra_valid_bits"));
.filter(|att| !att.path().is_ident("extra_valid_bits") && !att.path().is_ident("repr"));

let vis = item.vis;
let name = item.ident;
Expand All @@ -48,11 +49,27 @@ impl Bitflag {
.attrs
.iter()
.filter(|att| {
!att.path().is_ident("derive") && !att.path().is_ident("extra_valid_bits")
!att.path().is_ident("derive")
&& !att.path().is_ident("extra_valid_bits")
&& !att.path().is_ident("repr")
})
.cloned()
.collect();

let repr_attr = item
.attrs
.iter()
.find(|att| att.path().is_ident("repr"))
.map(|att| syn::parse2::<ReprAttr>(att.meta.to_token_stream()));

let repr_attr = match repr_attr {
Some(repr) => {
let repr = repr?;
Some(repr)
}
None => None,
};

let valid_bits_attr = item
.attrs
.iter()
Expand Down Expand Up @@ -212,6 +229,7 @@ impl Bitflag {
name,
inner_ty: ty,
derived_traits,
repr_attr,
impl_debug,
impl_serialize,
impl_deserialize,
Expand All @@ -232,6 +250,7 @@ impl ToTokens for Bitflag {
attrs,
name,
inner_ty,
repr_attr,
derived_traits,
impl_debug,
impl_serialize,
Expand Down Expand Up @@ -268,6 +287,13 @@ impl ToTokens for Bitflag {
}
};

let repr_attr = match repr_attr {
Some(repr) => {
quote! {#repr}
}
None => quote! {#[repr(transparent)]},
};

let const_mut = if cfg!(feature = "const-mut-ref") {
quote!(mut)
} else {
Expand Down Expand Up @@ -377,7 +403,7 @@ impl ToTokens for Bitflag {

let doc_from_iter = format!("Create a `{name}` from a iterator of flags.");
let generated = quote! {
#[repr(transparent)]
#repr_attr
#(#attrs)*
#[derive(#(#derived_traits,)*)]
#vis struct #name(#inner_ty)
Expand Down Expand Up @@ -919,6 +945,99 @@ impl Parse for ExtraValidBits {
}
}

struct ReprAttr {
path: Path,
_paren_token: Paren,
kind: ReprKind,
}

impl Parse for ReprAttr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let repr: syn::MetaList = input.parse()?;

if !repr.path.is_ident("repr") {
return Err(Error::new(repr.path.span(), "not a repr"));
}

let _paren_token = match repr.delimiter {
syn::MacroDelimiter::Paren(paren) => paren,
syn::MacroDelimiter::Brace(b) => {
return Err(Error::new(
b.span.span(),
"invalid syntax, expected parenthesis",
))
}
syn::MacroDelimiter::Bracket(b) => {
return Err(Error::new(
b.span.span(),
"invalid syntax, expected parenthesis",
))
}
};

let kind: ReprKind = syn::parse2(repr.tokens)?;

Ok(Self {
path: repr.path,
_paren_token,
kind,
})
}
}

impl ToTokens for ReprAttr {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { path, kind, .. } = self;
tokens.append_all(quote! {#[#path(#kind)]});
}
}

/// Supported repr
enum ReprKind {
C,
Rust,
Transparent,
}

impl Parse for ReprKind {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let meta: Meta = input.parse()?;

match meta {
Meta::Path(path) => {
let text = path
.get_ident()
.map(|p| p.to_string())
.unwrap_or("".to_string());

match text.as_str() {
"C" => Ok(Self::C),
"Rust" => Ok(Self::Rust),
"transparent" => Ok(Self::Transparent),
_ => Err(Error::new(
path.span(),
"`bitflag` unsupported repr: Supported repr are `C`, `Rust` and `transparent`",
)),
}
}
_ => Err(Error::new(
meta.span(),
"`bitflag` unsupported repr: Supported repr are `C`, `Rust` and `transparent`",
)),
}
}
}

impl ToTokens for ReprKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ReprKind::C => tokens.append_all(quote!(C)),
ReprKind::Rust => tokens.append_all(quote!(Rust)),
ReprKind::Transparent => tokens.append_all(quote!(transparent)),
}
}
}

/// Recursively check if a expression can be simplified to a simple wrap of `Self(<expr>)`.
///
/// Logic behind this:
Expand Down
45 changes: 45 additions & 0 deletions tests/00-smoke
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,49 @@ pub enum SmokeTest3 {
Flag9 = 12u8 as u32,
}

#[bitflag(u32)]
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum SmokeTest4 {
Flag1 = 1 << 9,
Flag2 = 1 << 12,
Flag3 = CONST1,
Flag4 = !CONST1,
Flag5 = CONST1 | CONST2 | 3,
Flag6 = Flag1 | Flag2,
Flag7 = CONST1 | Flag1,
Flag8 = (1 << 1) | (1 << 4),
Flag9 = 12u8 as u32,
}

#[bitflag(u32)]
#[repr(Rust)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum SmokeTest5 {
Flag1 = 1 << 9,
Flag2 = 1 << 12,
Flag3 = CONST1,
Flag4 = !CONST1,
Flag5 = CONST1 | CONST2 | 3,
Flag6 = Flag1 | Flag2,
Flag7 = CONST1 | Flag1,
Flag8 = (1 << 1) | (1 << 4),
Flag9 = 12u8 as u32,
}

#[bitflag(u32)]
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum SmokeTes6 {
Flag1 = 1 << 9,
Flag2 = 1 << 12,
Flag3 = CONST1,
Flag4 = !CONST1,
Flag5 = CONST1 | CONST2 | 3,
Flag6 = Flag1 | Flag2,
Flag7 = CONST1 | Flag1,
Flag8 = (1 << 1) | (1 << 4),
Flag9 = 12u8 as u32,
}

fn main() {}
21 changes: 21 additions & 0 deletions tests/06-invalid_repr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use bitflag_attr::bitflag;

#[bitflag(u8)]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum Test {
Flag1 = 1 << 0,
Flag2 = 1 << 1,
Flag3 = Flag1 | Flag2,
}

#[bitflag(u8)]
#[repr(packed)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum Test2 {
Flag1 = 1 << 0,
Flag2 = 1 << 1,
Flag3 = Flag1 | Flag2,
}

fn main() {}
11 changes: 11 additions & 0 deletions tests/06-invalid_repr.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: `bitflag` unsupported repr: Supported repr are `C`, `Rust` and `transparent`
--> tests/06-invalid_repr:4:8
|
4 | #[repr(u8)]
| ^^

error: `bitflag` unsupported repr: Supported repr are `C`, `Rust` and `transparent`
--> tests/06-invalid_repr:13:8
|
13 | #[repr(packed)]
| ^^^^^^
1 change: 1 addition & 0 deletions tests/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ fn tests() {
t.compile_fail("tests/03-too_many_args");
t.compile_fail("tests/04-repetitive_args");
t.pass("tests/05-no_std");
t.compile_fail("tests/06-invalid_repr");
}

0 comments on commit 8adeaae

Please sign in to comment.