libdisplay-info-derive-0.1.1/.cargo_vcs_info.json0000644000000001640000000000100153750ustar { "git": { "sha1": "6a05eb0b56b5e9d4072479cb14c6cdbd5a83ebe6" }, "path_in_vcs": "libdisplay-info-derive" }libdisplay-info-derive-0.1.1/Cargo.lock0000644000000021500000000000100133450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "libdisplay-info-derive" version = "0.1.1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" libdisplay-info-derive-0.1.1/Cargo.toml0000644000000023370000000000100133770ustar # 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" rust-version = "1.70.0" name = "libdisplay-info-derive" version = "0.1.1" authors = ["Christian Meissl "] build = false autolib = 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.1/Cargo.toml.orig000064400000000000000000000010271046102023000170530ustar 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.1" keywords = ["libdisplay", "DisplayID", "EDID"] license = "MIT" name = "libdisplay-info-derive" repository = "https://github.com/Smithay/libdisplay-info-rs" rust-version = "1.70.0" [lib] proc-macro = true [dependencies] syn = "2.0" quote = "1.0" proc-macro2 = "1.0"libdisplay-info-derive-0.1.1/src/lib.rs000064400000000000000000000362541046102023000161010ustar 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 { if let Some(cast_as) = field.cast_as.as_ref() { quote! { if value.#ident.is_null() { None } else { Some(#option_type::from(unsafe { *(value.#ident as #cast_as) })) } } } else { 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, }) }