libdisplay-info-derive-0.1.0/.cargo_vcs_info.json0000644000000001640000000000100153740ustar { "git": { "sha1": "5f7db335f31e4019bf0930b261a45c8e318ff741" }, "path_in_vcs": "libdisplay-info-derive" }libdisplay-info-derive-0.1.0/Cargo.toml0000644000000022670000000000100134000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "libdisplay-info-derive" version = "0.1.0" authors = ["Christian Meissl "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Utility crate for managing FFI bindings in libdisplay-info." documentation = "https://docs.rs/libdisplay-info-derive/" readme = false keywords = [ "libdisplay", "DisplayID", "EDID", ] categories = ["api-bindings"] license = "MIT" repository = "https://github.com/Smithay/libdisplay-info-rs" [lib] name = "libdisplay_info_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" libdisplay-info-derive-0.1.0/Cargo.toml.orig000064400000000000000000000007771046102023000170650ustar 00000000000000[package] authors = ["Christian Meissl "] categories = ["api-bindings"] description = "Utility crate for managing FFI bindings in libdisplay-info." documentation = "https://docs.rs/libdisplay-info-derive/" edition = "2021" version = "0.1.0" keywords = ["libdisplay", "DisplayID", "EDID"] license = "MIT" name = "libdisplay-info-derive" repository = "https://github.com/Smithay/libdisplay-info-rs" [lib] proc-macro = true [dependencies] syn = "2.0" quote = "1.0" proc-macro2 = "1.0"libdisplay-info-derive-0.1.0/src/lib.rs000064400000000000000000000351451046102023000160760ustar 00000000000000use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{ braced, parse_macro_input, Attribute, Expr, Ident, Path, Result, Token, Type, Visibility, }; struct FFIFrom { path: Path, ident: Ident, item: FFIItem, wrap: bool, } enum FFIItem { Struct(Punctuated), Enum(Punctuated), } struct FFIStructField { ident: Ident, ty: Type, cast_as: Option, optional: Option, ptr_deref: bool, span: Span, } struct FFIEnumVariant { ident: Ident, discriminant: Option, } #[proc_macro_derive(FFIFrom, attributes(ffi, cast_as, other, optional, ptr_deref, wrap))] pub fn ffi_from_fn(input: TokenStream) -> TokenStream { let FFIFrom { path, ident, item, wrap, } = parse_macro_input!(input as FFIFrom); match item { FFIItem::Struct(fields) => { let mapped_fields: Result> = fields .into_iter() .map(|field| { let ident = field.ident; let val = if let Type::Array(ref array) = field.ty { let elem = &array.elem; let num = match array.len { syn::Expr::Lit(ref lit) => match lit.lit { syn::Lit::Int(ref int) => int.base10_parse::()?, _ => { return Err(syn::Error::new( field.span, "only int literals are supported", )) } }, _ => { return Err(syn::Error::new( field.span, "only int literals are supported", )) } }; let vals = (0..num) .map(|idx| { if let Some(cast_as) = field.cast_as.as_ref() { quote! { #elem::from(value.#ident[#idx] as #cast_as) } } else { quote! { #elem::from(value.#ident[#idx]) } } }) .collect::>(); quote! { [ #(#vals ,)* ] } } else { let ty = field.ty; let is_option = matches!(ty, Type::Path(ref path) if path.path.segments[0].ident == "Option"); if !is_option && field.optional.is_some() { return Err(syn::Error::new( field.span, "#[optional()] only allowed for Option fields", )); } if is_option { let Type::Path(ref path) = ty else { unreachable!() }; let option_type = match path.path.segments[0].arguments { syn::PathArguments::AngleBracketed(ref bracketed) => { let arg = bracketed.args.first().ok_or_else(|| { syn::Error::new( field.span, "expected a single bracketed type", ) })?; match arg { syn::GenericArgument::Type(ty) => ty, _ => { return Err(syn::Error::new( field.span, "expected a single bracketed type", )) } } } _ => { return Err(syn::Error::new( field.span, "expected a single bracketed type", )) } }; if let Some(optional) = field.optional { if let Some(cast_as) = field.cast_as.as_ref() { quote! { if value.#ident == #optional { None } else { Some(#option_type::from(value.#ident as #cast_as)) } } } else { quote! { if value.#ident == #optional { None } else { Some(#option_type::from(value.#ident)) } } } } else if field.ptr_deref { quote! { if value.#ident.is_null() { None } else { Some(#option_type::from(unsafe { *value.#ident })) } } } else { return Err(syn::Error::new( field.span, "#[optional()] is required for non pointer types", )); } } else if let Some(cast_as) = field.cast_as.as_ref() { quote! { #ty::from(value.#ident as #cast_as) } } else { quote! { #ty::from(value.#ident) } } }; Ok(quote! { #ident: #val }) }) .collect(); let mapped_fields = match mapped_fields { Ok(fields) => fields, Err(err) => return TokenStream::from(err.into_compile_error()), }; let expanded = if wrap { let ref_ident = quote::format_ident!("{}Ref", ident); let ref_doc = format!("Reference for [`{}`]", ident); let inner_doc = format!("Access the inner [`{}`]", ident); quote! { impl From<#path> for #ident { #[inline] fn from(value: #path) -> #ident { Self { #(#mapped_fields ,)* } } } #[doc = #ref_doc] #[derive(Debug)] #[repr(transparent)] pub struct #ref_ident(*const #path); impl #ref_ident { #[doc = #inner_doc] pub fn inner(&self) -> #ident { #ident::from(unsafe { *self.0 }) } } impl #ident { pub(crate) fn from_ptr(ptr: *const #path) -> Option<#ident> { if ptr.is_null() { None } else { Some(Self::from(unsafe { *ptr })) } } } impl #ref_ident { pub(crate) fn from_ptr(ptr: *const #path) -> Option<#ref_ident> { if ptr.is_null() { None } else { Some(Self(ptr)) } } } } } else { quote! { impl From<#path> for #ident { #[inline] fn from(value: #path) -> #ident { Self { #(#mapped_fields ,)* } } } impl #ident { pub(crate) fn from_ptr(ptr: *const #path) -> Option<#ident> { if ptr.is_null() { None } else { Some(Self::from(unsafe { *ptr })) } } } } }; TokenStream::from(expanded) } FFIItem::Enum(variants) => { let mapped_variants = variants .iter() .filter(|variant| variant.discriminant.is_some()) .map(|variant| { let variant_ident = &variant.ident; let disc = &variant.discriminant; quote! { #disc => #ident::#variant_ident } }) .collect::>(); let other = variants .iter() .find(|variant| variant.discriminant.is_none()); let tail = if let Some(other) = other { let other_ident = &other.ident; quote! { _ => #ident::#other_ident, } } else { quote! { _ => unreachable!(), } }; let expanded = quote! { impl From<#path> for #ident { #[inline] fn from(value: #path) -> #ident { match value { #(#mapped_variants ,)* #tail } } } }; TokenStream::from(expanded) } } } impl Parse for FFIFrom { fn parse(input: ParseStream) -> Result { let attributes = input.call(Attribute::parse_outer)?; let path = attributes .iter() .find(|attr| attr.path().segments[0].ident == "ffi") .ok_or_else(|| input.error("#[ffi()] attribute is required"))? .parse_args::()?; let wrap = attributes .iter() .any(|attr| attr.path().segments[0].ident == "wrap"); input.parse::()?; let lookahead = input.lookahead1(); if lookahead.peek(Token![struct]) { input.parse::()?; let ident = input.parse::()?; let fields = { let content; braced!(content in input); content.parse_terminated(parse_ffi_struct_field, Token![,])? }; Ok(FFIFrom { path, ident, item: FFIItem::Struct(fields), wrap, }) } else if lookahead.peek(Token![enum]) { input.parse::()?; let ident = input.parse::()?; let variants = { let content; braced!(content in input); content.parse_terminated(parse_ffi_enum_variant, Token![,])? }; if variants .iter() .filter(|attr| attr.discriminant.is_none()) .count() > 1 { return Err(input.error("only a single variant marked with #[other] is supported")); } Ok(FFIFrom { path, ident, item: FFIItem::Enum(variants), wrap, }) } else { Err(lookahead.error()) } } } fn parse_ffi_struct_field(input: ParseStream) -> Result { let attributes = input.call(Attribute::parse_outer)?; let cast_as = attributes .iter() .find(|attr| attr.path().segments[0].ident == "cast_as"); let cast_as = if let Some(cast_as) = cast_as { Some(cast_as.parse_args::()?) } else { None }; let optional = attributes .iter() .find(|attr| attr.path().segments[0].ident == "optional"); let ptr_deref = attributes .iter() .any(|attr| attr.path().segments[0].ident == "ptr_deref"); let optional = if let Some(optional) = optional { Some(optional.parse_args::()?) } else { None }; input.parse::()?; let ident = input.parse::()?; input.parse::()?; let ty = input.parse::()?; Ok(FFIStructField { ident, ty, cast_as, optional, ptr_deref, span: input.span(), }) } fn parse_ffi_enum_variant(input: ParseStream) -> Result { let is_other = input .call(Attribute::parse_outer)? .into_iter() .any(|attr| attr.path().segments[0].ident == "other"); input.parse::()?; let ident: Ident = input.parse()?; let discriminant = if is_other { None } else { input.parse::()?; Some(input.parse::()?) }; Ok(FFIEnumVariant { ident, discriminant, }) }