Skip to content

Commit 7e42cde

Browse files
committed
glib-macros: prototype porting wrapper to a proc macro
1 parent 94f1e36 commit 7e42cde

File tree

4 files changed

+765
-2
lines changed

4 files changed

+765
-2
lines changed

glib-macros/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod object_subclass_attribute;
1313
mod properties;
1414
mod shared_boxed_derive;
1515
mod variant_derive;
16+
mod wrapper_attribute;
1617

1718
mod utils;
1819

@@ -972,3 +973,13 @@ pub fn clone_block(_attr: TokenStream, item: TokenStream) -> TokenStream {
972973
clone_block_attribute::impl_clone_block(&mut item, &syn::parse_quote! { #glib }, &errors);
973974
errors.output_with(item).into()
974975
}
976+
977+
#[proc_macro_attribute]
978+
#[proc_macro_error]
979+
pub fn wrapper(attr: TokenStream, item: TokenStream) -> TokenStream {
980+
let input = parse_macro_input!(item as syn::ItemStruct);
981+
match wrapper_attribute::impl_wrapper(attr.into(), input) {
982+
Ok(gen) => gen.into(),
983+
Err(e) => e.into_compile_error().into(),
984+
}
985+
}

glib-macros/src/wrapper_attribute.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use std::collections::HashSet;
4+
5+
use proc_macro2::TokenStream;
6+
use quote::ToTokens;
7+
use syn::spanned::Spanned;
8+
9+
mod boxed;
10+
#[allow(dead_code)]
11+
mod boxed_inline {
12+
#[derive(deluxe::ParseMetaItem)]
13+
pub struct BoxedInline {
14+
copy: Option<syn::Expr>,
15+
free: Option<syn::Expr>,
16+
init: Option<syn::Expr>,
17+
copy_into: Option<syn::Expr>,
18+
clear: Option<syn::Expr>,
19+
r#type: Option<syn::Expr>,
20+
#[deluxe(flatten)]
21+
args: super::WrapperArgs,
22+
}
23+
}
24+
#[allow(dead_code)]
25+
mod shared {
26+
#[derive(deluxe::ParseMetaItem)]
27+
pub struct Shared {
28+
r#ref: syn::Expr,
29+
unref: syn::Expr,
30+
r#type: Option<syn::Expr>,
31+
#[deluxe(flatten)]
32+
args: super::WrapperArgs,
33+
}
34+
}
35+
#[allow(dead_code)]
36+
mod object {
37+
#[derive(deluxe::ParseMetaItem)]
38+
pub struct Object {
39+
#[deluxe(append)]
40+
extends: Vec<syn::Path>,
41+
#[deluxe(append)]
42+
implements: Vec<syn::Path>,
43+
r#type: syn::Expr,
44+
#[deluxe(flatten)]
45+
args: super::WrapperArgs,
46+
}
47+
}
48+
#[allow(dead_code)]
49+
mod object_subclass {
50+
#[derive(deluxe::ParseMetaItem)]
51+
pub struct ObjectSubclass {
52+
#[deluxe(append)]
53+
extends: Vec<syn::Path>,
54+
#[deluxe(append)]
55+
implements: Vec<syn::Path>,
56+
#[deluxe(flatten)]
57+
args: super::WrapperArgs,
58+
}
59+
}
60+
#[allow(dead_code)]
61+
mod interface {
62+
#[derive(deluxe::ParseMetaItem)]
63+
pub struct Interface {
64+
#[deluxe(append)]
65+
requires: Vec<syn::Path>,
66+
r#type: syn::Expr,
67+
#[deluxe(flatten)]
68+
args: super::WrapperArgs,
69+
}
70+
}
71+
#[allow(dead_code)]
72+
mod object_interface {
73+
#[derive(deluxe::ParseMetaItem)]
74+
pub struct ObjectInterface {
75+
#[deluxe(append)]
76+
requires: Vec<syn::Path>,
77+
#[deluxe(flatten)]
78+
args: super::WrapperArgs,
79+
}
80+
}
81+
82+
#[derive(deluxe::ParseMetaItem)]
83+
struct WrapperArgs {
84+
#[deluxe(
85+
append,
86+
rename = skip_trait,
87+
map = |s: HashSet<syn::Ident>| s.into_iter().map(|s| s.to_string()).collect(),
88+
)]
89+
skipped_traits: HashSet<String>,
90+
}
91+
92+
enum Member<'a> {
93+
Named(&'a syn::Ident),
94+
Unnamed(syn::Index),
95+
}
96+
97+
impl<'a> Member<'a> {
98+
fn from_fields(fields: &'a syn::Fields) -> impl Iterator<Item = Member<'a>> {
99+
fields
100+
.iter()
101+
.enumerate()
102+
.map(|(index, field)| match &field.ident {
103+
Some(ident) => Member::Named(ident),
104+
None => Member::Unnamed(syn::Index {
105+
index: index as u32,
106+
span: field.ty.span(),
107+
}),
108+
})
109+
}
110+
}
111+
112+
impl<'a> ToTokens for Member<'a> {
113+
#[inline]
114+
fn to_tokens(&self, tokens: &mut TokenStream) {
115+
match self {
116+
Self::Named(ident) => ident.to_tokens(tokens),
117+
Self::Unnamed(index) => index.to_tokens(tokens),
118+
}
119+
}
120+
}
121+
122+
fn construct_stmt(
123+
fields: &syn::Fields,
124+
first_func: impl FnOnce(Member<'_>) -> TokenStream,
125+
mut rest_func: impl FnMut(Member<'_>) -> TokenStream,
126+
) -> TokenStream {
127+
let mut first_func = Some(first_func);
128+
let members = fields.iter().enumerate().map(move |(index, f)| {
129+
let member = match &f.ident {
130+
Some(ident) => Member::Named(ident),
131+
None => Member::Unnamed(syn::Index {
132+
index: index as u32,
133+
span: f.ty.span(),
134+
}),
135+
};
136+
let expr: TokenStream = if index == 0 {
137+
(first_func.take().unwrap())(member)
138+
} else {
139+
rest_func(member)
140+
};
141+
match &f.ident {
142+
Some(ident) => quote::quote_spanned! { f.ty.span() => #ident: #expr },
143+
None => expr,
144+
}
145+
});
146+
match fields {
147+
syn::Fields::Named(_) => quote::quote! { Self { #(#members),* } },
148+
syn::Fields::Unnamed(_) => quote::quote! { Self(#(#members),*) },
149+
_ => unreachable!(),
150+
}
151+
}
152+
153+
#[inline]
154+
fn unique_lifetime(name: &str, generics: &syn::Generics) -> syn::Lifetime {
155+
let mut ident = String::from(name);
156+
while generics.lifetimes().any(|l| l.lifetime.ident == ident) {
157+
ident.push('_');
158+
}
159+
ident.insert(0, '\'');
160+
syn::Lifetime::new(&ident, proc_macro2::Span::mixed_site())
161+
}
162+
163+
#[inline]
164+
fn insert_lifetime(name: &str, generics: &mut syn::Generics) -> syn::Lifetime {
165+
let lt = unique_lifetime(name, generics);
166+
generics.params.insert(0, syn::parse_quote! { #lt });
167+
lt
168+
}
169+
170+
fn get_first_field_type_param(fields: &syn::Fields, type_index: usize) -> syn::Result<&syn::Type> {
171+
fields
172+
.iter()
173+
.next()
174+
.ok_or_else(|| fields.span())
175+
.and_then(|f| match &f.ty {
176+
syn::Type::Path(tp) => tp
177+
.path
178+
.segments
179+
.last()
180+
.ok_or_else(|| tp.span())
181+
.and_then(|s| match &s.arguments {
182+
syn::PathArguments::AngleBracketed(ga) => ga
183+
.args
184+
.iter()
185+
.nth(type_index)
186+
.ok_or_else(|| ga.span())
187+
.and_then(|a| match a {
188+
syn::GenericArgument::Type(t) => Ok(t),
189+
t => Err(t.span()),
190+
}),
191+
a => Err(a.span()),
192+
}),
193+
ty => Err(ty.span()),
194+
})
195+
.map_err(|span| {
196+
syn::Error::new(
197+
span,
198+
format_args!("First field missing type argument {type_index}"),
199+
)
200+
})
201+
}
202+
203+
#[inline]
204+
fn get_type_name(ty: &syn::Type) -> Option<&syn::Ident> {
205+
match ty {
206+
syn::Type::Path(tp) => tp.path.segments.last().map(|s| &s.ident),
207+
_ => None,
208+
}
209+
}
210+
211+
fn add_repr_transparent(item: &mut syn::ItemStruct) {
212+
if item.fields.iter().skip(1).all(|f| {
213+
get_type_name(&f.ty)
214+
.map(|i| i == "PhantomData")
215+
.unwrap_or(false)
216+
}) {
217+
item.attrs.extend(
218+
syn::parse::Parser::parse_str(syn::Attribute::parse_outer, "#[repr(transparent)]")
219+
.unwrap(),
220+
);
221+
}
222+
}
223+
224+
pub fn impl_wrapper(attr: TokenStream, mut item: syn::ItemStruct) -> syn::Result<TokenStream> {
225+
let first_field = match item.fields.iter().next() {
226+
Some(f) => f,
227+
None => {
228+
return Err(syn::Error::new_spanned(
229+
item,
230+
"Wrapper struct must have at least one field",
231+
));
232+
}
233+
};
234+
let type_name = match get_type_name(&first_field.ty) {
235+
Some(n) => n,
236+
None => {
237+
return Err(syn::Error::new_spanned(
238+
item,
239+
"First field must be a type path",
240+
))
241+
}
242+
};
243+
244+
let mut tokens = match type_name.to_string().as_str() {
245+
"Boxed" => deluxe::parse2::<boxed::Boxed>(attr)?.into_token_stream(&mut item)?,
246+
_ => return Err(syn::Error::new_spanned(type_name, "Unknown wrapper type")),
247+
};
248+
item.to_tokens(&mut tokens);
249+
Ok(tokens)
250+
}

0 commit comments

Comments
 (0)