Skip to content

Commit

Permalink
Add initial derive(Encode, RefEncode) implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Jun 16, 2022
1 parent 1b3c9e9 commit 6f32a28
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 11 deletions.
25 changes: 25 additions & 0 deletions objc2-encode/tests/test_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,28 @@ fn cgpoint() {
assert_eq!(CGPoint::ENCODING, enc);
assert_eq!(CGPoint::ENCODING_REF, Encoding::Pointer(&enc));
}

#[derive(Encode, RefEncode)]
#[repr(transparent)]
struct Transparent {
_inner: usize,
}

#[test]
fn transparent() {
assert_eq!(Transparent::ENCODING, usize::ENCODING);
assert_eq!(Transparent::ENCODING_REF, usize::ENCODING_REF);
}

#[derive(Encode, RefEncode)]
#[repr(usize)]
enum MyEnum {
_A = 1,
_B = 2,
}

#[test]
fn enum_repr() {
assert_eq!(MyEnum::ENCODING, usize::ENCODING);
assert_eq!(MyEnum::ENCODING_REF, usize::ENCODING_REF);
}
76 changes: 65 additions & 11 deletions objc2-proc-macros/src/derive.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,78 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::{Data, DeriveInput};

use crate::utils::get_repr;

pub(crate) fn impl_encode(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
unsafe impl ::objc2_encode::Encode for #name {
const ENCODING: ::objc2_encode::Encoding<'static> = ::objc2_encode::Encoding::Struct(
stringify!(#name),
&[CGFloat::ENCODING, CGFloat::ENCODING],
);
let DeriveInput {
ident, data, attrs, ..
} = ast;

let repr = match get_repr(&attrs) {
Some(repr) => repr,
None => panic!("Missing repr"),
};

let encoding = match data {
Data::Struct(data) => {
let fields = data
.fields
.iter()
.map(|field| &field.ty)
.collect::<Vec<_>>();
match &*repr.to_string() {
"transparent" => {
let field = fields[0];
assert_eq!(fields.len(), 1, "Expected one item");
quote!(<#field as ::objc2_encode::Encode>::ENCODING)
}
"C" => {
quote!(
::objc2_encode::Encoding::Struct(
stringify!(#ident),
&[#(<#fields as ::objc2_encode::Encode>::ENCODING),*],
)
)
}
_ => panic!("Unknown repr"),
}
}
Data::Enum(_) => {
let ty = match &*repr.to_string() {
"usize" => quote! { core::primitive::usize },
"isize" => quote! { core::primitive::isize },
"u64" => quote! { core::primitive::u64 },
"i64" => quote! { core::primitive::i64 },
"u32" => quote! { core::primitive::u32 },
"i32" => quote! { core::primitive::i32 },
"u16" => quote! { core::primitive::u16 },
"i16" => quote! { core::primitive::i16 },
"u8" => quote! { core::primitive::u8 },
"i8" => quote! { core::primitive::i8 },
_ => panic!("Unknown repr"),
};
quote! { <#ty as ::objc2_encode::Encode>::ENCODING }
}
Data::Union(_) => unimplemented!(),
};
gen.into()

// TODO: Generics
quote! {
unsafe impl ::objc2_encode::Encode for #ident {
const ENCODING: ::objc2_encode::Encoding<'static> = #encoding;
}
}
.into()
}

pub(crate) fn impl_ref_encode(ast: &DeriveInput) -> TokenStream {
let name = &ast.ident;
let DeriveInput { ident, .. } = ast;
// TODO: Generics
// TODO: Objects

let gen = quote! {
unsafe impl ::objc2_encode::RefEncode for #name {
unsafe impl ::objc2_encode::RefEncode for #ident {
const ENCODING_REF: ::objc2_encode::Encoding<'static> = ::objc2_encode::Encoding::Pointer(
&<Self as ::objc2_encode::Encode>::ENCODING
);
Expand Down
1 change: 1 addition & 0 deletions objc2-proc-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extern "C" {}
use proc_macro::TokenStream;

mod derive;
mod utils;

/// TODO
#[proc_macro_derive(Encode)]
Expand Down
30 changes: 30 additions & 0 deletions objc2-proc-macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use syn::{Attribute, Ident, Meta, NestedMeta};

// Taken from `nom-derive`:
// https://github.com/rust-bakery/nom-derive/blob/5315891a0016b15094d4d0201f7d3ac803e4fc57/nom-derive-impl/src/enums.rs#L60-L90
pub(crate) fn get_repr(attrs: &[Attribute]) -> Option<Ident> {
for attr in attrs {
if let Ok(Meta::List(metalist)) = attr.parse_meta() {
if let Some(ident) = metalist.path.get_ident() {
if ident == "repr" {
for n in metalist.nested.iter() {
match n {
NestedMeta::Meta(meta) => match meta {
Meta::Path(path) => {
if let Some(word) = path.get_ident() {
return Some(word.clone());
} else {
panic!("unsupported nested type for 'repr'")
}
}
_ => panic!("unsupported nested type for 'repr'"),
},
_ => panic!("unsupported meta type for 'repr'"),
}
}
}
}
}
}
None
}

0 comments on commit 6f32a28

Please sign in to comment.