From c15e1a24709fd415919bc8b603802fabd651254b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Jan 2022 01:04:59 +0100 Subject: [PATCH 1/3] Add skeleton and test for Encode/RefEncode macros --- .github/workflows/ci.yml | 2 +- objc2-encode/Cargo.toml | 6 ++++++ objc2-encode/src/lib.rs | 2 ++ objc2-encode/tests/test_derive.rs | 22 ++++++++++++++++++++++ objc2-proc-macros/Cargo.toml | 5 +++++ objc2-proc-macros/src/lib.rs | 16 ++++++++++++++++ 6 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 objc2-encode/tests/test_derive.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39ff86ae0..65e89b53f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,7 +153,7 @@ jobs: ARGS: --no-default-features --features ${{ matrix.runtime || 'apple' }} ${{ matrix.args }} # Use --no-fail-fast, except with dinghy TESTARGS: ${{ matrix.dinghy && ' ' || '--no-fail-fast' }} ${{ matrix.test-args }} - FEATURES: ${{ matrix.features || 'malloc,block,exception,catch_all,verify_message' }} + FEATURES: ${{ matrix.features || 'malloc,block,exception,catch_all,verify_message,derive' }} UNSTABLE_FEATURES: ${{ matrix.unstable-features || 'unstable-autoreleasesafe' }} runs-on: ${{ matrix.os }} diff --git a/objc2-encode/Cargo.toml b/objc2-encode/Cargo.toml index f8c9d8d26..2b70870d1 100644 --- a/objc2-encode/Cargo.toml +++ b/objc2-encode/Cargo.toml @@ -18,6 +18,12 @@ repository = "https://github.com/madsmtm/objc2" documentation = "https://docs.rs/objc2-encode/" license = "MIT" +[features] +derive = ["objc2-proc-macros"] + +[dependencies] +objc2-proc-macros = { path = "../objc2-proc-macros", optional = true } + [package.metadata.docs.rs] default-target = "x86_64-apple-darwin" diff --git a/objc2-encode/src/lib.rs b/objc2-encode/src/lib.rs index ac4e170f8..d59ad56d6 100644 --- a/objc2-encode/src/lib.rs +++ b/objc2-encode/src/lib.rs @@ -115,3 +115,5 @@ mod static_str; pub use self::encode::{Encode, EncodeArguments, RefEncode}; pub use self::encoding::Encoding; +#[cfg(feature = "derive")] +pub use objc2_proc_macros::{Encode, RefEncode}; diff --git a/objc2-encode/tests/test_derive.rs b/objc2-encode/tests/test_derive.rs new file mode 100644 index 000000000..ae2abbb7f --- /dev/null +++ b/objc2-encode/tests/test_derive.rs @@ -0,0 +1,22 @@ +#![cfg(feature = "derive")] +use objc2_encode::{Encode, Encoding, RefEncode}; + +#[cfg(target_pointer_width = "32")] +type CGFloat = f32; + +#[cfg(target_pointer_width = "64")] +type CGFloat = f64; + +#[derive(Encode, RefEncode)] +#[repr(C)] +struct CGPoint { + x: CGFloat, + y: CGFloat, +} + +#[test] +fn cgpoint() { + let enc = Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFloat::ENCODING]); + assert_eq!(CGPoint::ENCODING, enc); + assert_eq!(CGPoint::ENCODING_REF, Encoding::Pointer(&enc)); +} diff --git a/objc2-proc-macros/Cargo.toml b/objc2-proc-macros/Cargo.toml index 2e017b637..fd151d575 100644 --- a/objc2-proc-macros/Cargo.toml +++ b/objc2-proc-macros/Cargo.toml @@ -30,5 +30,10 @@ gnustep-1-9 = ["gnustep-1-8"] gnustep-2-0 = ["gnustep-1-9"] gnustep-2-1 = ["gnustep-2-0"] +[dependencies] +syn = "1.0" +quote = "1.0" +# TODO: Use `proc-macro-crate` or similar for better usage downstream? + [package.metadata.docs.rs] default-target = "x86_64-apple-darwin" diff --git a/objc2-proc-macros/src/lib.rs b/objc2-proc-macros/src/lib.rs index d31452f14..604e6c7a0 100644 --- a/objc2-proc-macros/src/lib.rs +++ b/objc2-proc-macros/src/lib.rs @@ -16,3 +16,19 @@ #[cfg(doctest)] #[doc = include_str!("../README.md")] extern "C" {} + +use proc_macro::TokenStream; + +/// TODO +#[proc_macro_derive(Encode)] +pub fn encode_derive(input: TokenStream) -> TokenStream { + dbg!(input); + todo!() +} + +/// TODO +#[proc_macro_derive(RefEncode)] +pub fn ref_encode_derive(input: TokenStream) -> TokenStream { + dbg!(input); + todo!() +} From 0f15f8aafc8819a2f2bd4ec7d3516ea28337b2d5 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Jan 2022 01:42:14 +0100 Subject: [PATCH 2/3] Add mocked derive implementation --- objc2-proc-macros/src/derive.rs | 28 ++++++++++++++++++++++++++++ objc2-proc-macros/src/lib.rs | 10 ++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 objc2-proc-macros/src/derive.rs diff --git a/objc2-proc-macros/src/derive.rs b/objc2-proc-macros/src/derive.rs new file mode 100644 index 000000000..4ee6aab4e --- /dev/null +++ b/objc2-proc-macros/src/derive.rs @@ -0,0 +1,28 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::DeriveInput; + +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], + ); + } + }; + gen.into() +} + +pub(crate) fn impl_ref_encode(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + let gen = quote! { + unsafe impl ::objc2_encode::RefEncode for #name { + const ENCODING_REF: ::objc2_encode::Encoding<'static> = ::objc2_encode::Encoding::Pointer( + &::ENCODING + ); + } + }; + gen.into() +} diff --git a/objc2-proc-macros/src/lib.rs b/objc2-proc-macros/src/lib.rs index 604e6c7a0..9572d3d60 100644 --- a/objc2-proc-macros/src/lib.rs +++ b/objc2-proc-macros/src/lib.rs @@ -19,16 +19,18 @@ extern "C" {} use proc_macro::TokenStream; +mod derive; + /// TODO #[proc_macro_derive(Encode)] pub fn encode_derive(input: TokenStream) -> TokenStream { - dbg!(input); - todo!() + let ast = syn::parse(input).unwrap(); + derive::impl_encode(&ast) } /// TODO #[proc_macro_derive(RefEncode)] pub fn ref_encode_derive(input: TokenStream) -> TokenStream { - dbg!(input); - todo!() + let ast = syn::parse(input).unwrap(); + derive::impl_ref_encode(&ast) } From 6f840ede197cae268e02e3fad3932a64662d8fa0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Jan 2022 18:48:46 +0100 Subject: [PATCH 3/3] Add initial derive(Encode, RefEncode) implementation --- objc2-encode/tests/test_derive.rs | 25 ++++++++++ objc2-proc-macros/src/derive.rs | 76 ++++++++++++++++++++++++++----- objc2-proc-macros/src/lib.rs | 1 + objc2-proc-macros/src/utils.rs | 30 ++++++++++++ 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 objc2-proc-macros/src/utils.rs diff --git a/objc2-encode/tests/test_derive.rs b/objc2-encode/tests/test_derive.rs index ae2abbb7f..fe9f7f308 100644 --- a/objc2-encode/tests/test_derive.rs +++ b/objc2-encode/tests/test_derive.rs @@ -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); +} diff --git a/objc2-proc-macros/src/derive.rs b/objc2-proc-macros/src/derive.rs index 4ee6aab4e..db1c9e97e 100644 --- a/objc2-proc-macros/src/derive.rs +++ b/objc2-proc-macros/src/derive.rs @@ -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::>(); + 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( &::ENCODING ); diff --git a/objc2-proc-macros/src/lib.rs b/objc2-proc-macros/src/lib.rs index 9572d3d60..5f13e13c5 100644 --- a/objc2-proc-macros/src/lib.rs +++ b/objc2-proc-macros/src/lib.rs @@ -20,6 +20,7 @@ extern "C" {} use proc_macro::TokenStream; mod derive; +mod utils; /// TODO #[proc_macro_derive(Encode)] diff --git a/objc2-proc-macros/src/utils.rs b/objc2-proc-macros/src/utils.rs new file mode 100644 index 000000000..571392922 --- /dev/null +++ b/objc2-proc-macros/src/utils.rs @@ -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 { + 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 +}