Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
andy128k committed Apr 27, 2021
1 parent 63f9f2f commit 2593a8c
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ config.status
*.o
*.swp
**Cargo.lock
gtk/tests/*
# gtk/tests/*
145 changes: 145 additions & 0 deletions gtk/tests/builder_handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#[derive(glib::Downgrade)]
pub struct MyWidget(gtk::Widget);

#[gtk3_macros::builder_handlers]
impl MyWidget {
fn handler0(&self) {}

fn handler1(&self, _p: u32) {}

fn handler2(&self, _x: f64, _y: f64) {}

fn handler3(&self, x: f64, y: f64) -> f64 {
x + y
}

// fn handler4(&self, x: f64, y: f64) -> Option<f64> {
// if x >= 0.0 && y >= 0.0 {
// Some(x + y)
// } else {
// None
// }
// }
}

// Generated code
/*
impl MyWidget {
#[allow(clippy)]
fn get_handler(
&self,
signal: &str,
) -> Option<Box<dyn Fn(&[glib::Value]) -> Option<glib::Value> + 'static>> {
match signal {
"handler0" => Some({
#[allow(unused_variables)]
Box::new(
glib::clone!(@weak self as this => move |values: &[glib::Value]| {
this.handler0();
None
}),
)
}),
"handler1" => Some({
#[allow(unused_variables)]
Box::new(
glib::clone!(@weak self as this => move |values: &[glib::Value]| {
this.handler1(
match values[0usize].get_some() {
Ok(value) => value,
Err(error) => {
glib::g_critical!(
"builder handler",
"Handler {} expects an argument of type {} but received `{:?}`: {}.",
"handler1", stringify! (u32),
values[0usize], error
);
return None;
},
}
);
None
}),
)
}),
"handler2" => Some({
#[allow(unused_variables)]
Box::new(
glib::clone!(@weak self as this => move |values: &[glib::Value]| {
this.handler2(
match values[0usize].get_some() {
Ok(value) => value,
Err(error) => {
glib::g_critical!(
"builder handler",
"Handler {} expects an argument of type {} but received `{:?}`: {}.",
"handler2",
stringify!(f64),
values[0usize],
error
);
return None;
},
},
match values[1usize].get_some() {
Ok(value) => value,
Err(error) => {
glib::g_critical!(
"builder handler",
"Handler {} expects an argument of type {} but received `{:?}`: {}.",
"handler2",
stringify!(f64),
values[1usize],
error
);
return None;
},
}
);
None
}),
)
}),
"handler3" => Some({
#[allow(unused_variables)]
Box::new(
glib::clone!(@weak self as this => move |values: &[glib::Value]| {
let result = this.handler3(
match values[0usize].get_some() {
Ok(value) => value,
Err(error) => {
glib::g_critical!(
"builder handler",
"Handler {} expects an argument of type {} but received `{:?}`: {}.",
"handler3",
stringify!(f64),
values[0usize],
error
);
return None;
},
},
match values[1usize].get_some() {
Ok(value) => value,
Err(error) => {
glib::g_critical!(
"builder handler",
"Handler {} expects an argument of type {} but received `{:?}`: {}.",
"handler3",
stringify!(f64),
values[1usize],
error
);
return None;
},
}
);
Some(glib::value::ToValue::to_value(&result))
}),
)
}),
_ => None,
}
}
}
*/
1 change: 1 addition & 0 deletions gtk3-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", default-features = false, features = ["full"] }
proc-macro-crate = "1.0"
darling = "0.12"
199 changes: 199 additions & 0 deletions gtk3-macros/src/builder_handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use darling::FromMeta;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, quote_spanned};
use syn::{
parse, spanned::Spanned, Attribute, Error, FnArg, ImplItem, ImplItemMethod, ItemImpl, Meta,
MetaList, NestedMeta, PatType, Signature, Type,
};

#[derive(Debug, Default, FromMeta)]
#[darling(default)]
pub struct HandlersImplAttributes {
get_handler_fn: Option<String>,
}

#[derive(Debug, Default, FromMeta)]
#[darling(default)]
struct HandlerAttributes {
name: Option<String>,
}

#[derive(Debug)]
struct HandlerInfo {
name: String,
sig: Signature,
}

fn generate_handler(info: &HandlerInfo) -> Result<TokenStream2, Error> {
let handler_name = &info.name;
let arguments: Vec<TokenStream2> = info.sig.inputs
.iter()
.skip(1)
.enumerate()
.map(|(index, arg)| {
let arg_type = argument_type(arg)?;
Ok(quote_spanned! { arg.span() =>
match values[#index].get() {
Ok(value) => value,
Err(error) => {
glib::g_critical!("builder handler", "Handler {} expects an argument of type {} but received `{:?}`: {}.", #handler_name, stringify!(#arg_type), values[#index], error);
return None;
},
}
})
})
.collect::<Result<_, Error>>()?;

let signal = &info.name;
let method = &info.sig.ident;
let is_unit = matches!(info.sig.output, syn::ReturnType::Default);
let handler = if is_unit {
quote_spanned! { info.sig.span() =>
#signal => Some({
Box::new(glib::clone!(@weak self as this => @default-return None, move |values: &[glib::Value]| {
this.#method(#(#arguments),*);
None
}))
}),
}
} else {
quote_spanned! { info.sig.span() =>
#signal => Some({
Box::new(glib::clone!(@weak self as this => @default-return None, move |values: &[glib::Value]| {
let result = this.#method(#(#arguments),*);
Some(glib::value::ToValue::to_value(&result))
}))
}),
}
};
Ok(handler)
}

fn combine_errors(error_acc: &mut Option<Error>, error: Error) {
match error_acc {
Some(ref mut error_acc) => {
error_acc.combine(error);
}
None => {
error_acc.replace(error);
}
}
}

fn attributes_to_metas(attributes: Vec<Attribute>) -> Result<Vec<NestedMeta>, Error> {
let mut metas = Vec::new();
let mut error = None;
for attr in attributes {
let meta = attr.parse_meta()?;
match meta {
Meta::List(MetaList { nested, .. }) => metas.extend(nested),
_ => combine_errors(&mut error, Error::new(attr.span(), "Unexpected attribute")),
}
}
if let Some(error) = error {
Err(error)
} else {
Ok(metas)
}
}

fn is_assoc(sig: &Signature) -> bool {
sig.inputs
.first()
.map_or(false, |arg| matches!(arg, FnArg::Receiver(..)))
}

fn argument_type(arg: &FnArg) -> Result<&Type, Error> {
match arg {
FnArg::Typed(PatType { ty, .. }) => Ok(&*ty),
_ => Err(Error::new(
arg.span(),
"Cannot extract type of an argument.",
)),
}
}

fn generate_connect_method(
attrs: &HandlersImplAttributes,
actions: &[TokenStream2],
) -> ImplItemMethod {
let get_handler_fn = format_ident!(
"{}",
attrs.get_handler_fn.as_deref().unwrap_or("get_handler")
);
let builder_connect_method = quote! {
#[allow(clippy)]
#[allow(unused_variables, unused_braces)]
fn #get_handler_fn(&self, builder: &gtk::Builder, signal: &str) -> Option<Box<dyn Fn(&[glib::Value]) -> Option<glib::Value> + 'static>> {
match signal {
#(
#actions
)*
_ => None,
}
}
};
parse(builder_connect_method.into()).unwrap()
}

pub fn handlers(
attrs: HandlersImplAttributes,
mut input: ItemImpl,
) -> Result<TokenStream, TokenStream> {
let mut handlers: Vec<HandlerInfo> = Vec::new();
for item in input.items.iter_mut() {
if let ImplItem::Method(method) = item {
if !is_assoc(&method.sig) {
return Err(Error::new(
method.sig.span(),
"Unsupported signature of method. Only associated methods are supported.",
)
.to_compile_error()
.into());
}

let attributes =
extract_from_vec(&mut method.attrs, |attr| attr.path.is_ident("handler"));
let metas = attributes_to_metas(attributes).map_err(|err| err.to_compile_error())?;
let attrs = HandlerAttributes::from_list(&metas)
.map_err(|err| TokenStream::from(err.write_errors()))?;

let info = HandlerInfo {
name: attrs.name.unwrap_or_else(|| method.sig.ident.to_string()),
sig: method.sig.clone(),
};
handlers.push(info);
}
}

let connects: Vec<TokenStream2> = handlers
.iter()
.map(generate_handler)
.collect::<Result<_, _>>()
.map_err(|err| err.to_compile_error())?;

let connect_method = generate_connect_method(&attrs, &connects);
input.items.push(ImplItem::Method(connect_method));

let s = quote!(#input);
// println!("{}", s);
Ok(s.into())
}

// TODO: Replace this by Vec::drain_filter as soon as it is stabilized.
fn extract_from_vec<T>(vec: &mut Vec<T>, predicate: impl Fn(&T) -> bool) -> Vec<T> {
let mut i = 0;
let mut result: Vec<T> = Vec::new();
while i != vec.len() {
if (predicate)(&vec[i]) {
let item = vec.remove(i);
result.push(item);
} else {
i += 1;
}
}
result
}
18 changes: 17 additions & 1 deletion gtk3-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Take a look at the license at the top of the repository in the LICENSE file.

mod attribute_parser;
mod builder_handlers;
mod composite_template_derive;
mod util;

use darling::FromMeta;
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
use syn::{parse_macro_input, DeriveInput};
use syn::DeriveInput;
use syn::{parse_macro_input, AttributeArgs, ItemImpl};

/// Derive macro for using a composite template in a widget.
///
Expand Down Expand Up @@ -61,3 +64,16 @@ pub fn composite_template_derive(input: TokenStream) -> TokenStream {
let gen = composite_template_derive::impl_composite_template(&input);
gen.into()
}

#[proc_macro_attribute]
pub fn builder_handlers(args: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemImpl);
let attribute_args = parse_macro_input!(args as AttributeArgs);
let attrs = match builder_handlers::HandlersImplAttributes::from_list(&attribute_args) {
Ok(v) => v,
Err(e) => {
return TokenStream::from(e.write_errors());
}
};
builder_handlers::handlers(attrs, input).unwrap_or_else(|err| err)
}

0 comments on commit 2593a8c

Please sign in to comment.