-
-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
437 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
// Take a look at the license at the top of the repository in the LICENSE file. | ||
|
||
use crate::utils::crate_ident_new; | ||
use proc_macro2::{Ident, Span, TokenStream}; | ||
use quote::{quote, ToTokens, TokenStreamExt}; | ||
use syn::Token; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
enum CaptureKind { | ||
Weak, | ||
WeakAllowNone, | ||
Strong, | ||
} | ||
|
||
struct Capture { | ||
name: TokenStream, | ||
alias: Option<syn::Ident>, | ||
kind: CaptureKind, | ||
} | ||
|
||
impl Capture { | ||
fn alias(&self) -> TokenStream { | ||
if let Some(ref a) = self.alias { | ||
a.to_token_stream() | ||
} else { | ||
self.name.to_token_stream() | ||
} | ||
} | ||
fn outer_before_tokens(&self, crate_ident: &TokenStream) -> TokenStream { | ||
let alias = self.alias(); | ||
let name = &self.name; | ||
match self.kind { | ||
CaptureKind::Weak | CaptureKind::WeakAllowNone => quote! { | ||
let #alias = #crate_ident::clone::Downgrade::downgrade(&#name); | ||
}, | ||
CaptureKind::Strong => quote! { | ||
let #alias = #name.clone(); | ||
}, | ||
} | ||
} | ||
|
||
fn outer_after_tokens(&self, crate_ident: &TokenStream, closure_ident: &Ident) -> TokenStream { | ||
let name = &self.name; | ||
match self.kind { | ||
CaptureKind::Weak => quote! { | ||
#crate_ident::object::Object::watch_closure(&#name, &#closure_ident); | ||
}, | ||
_ => Default::default(), | ||
} | ||
} | ||
|
||
fn inner_before_tokens(&self, crate_ident: &TokenStream) -> TokenStream { | ||
let alias = self.alias(); | ||
match self.kind { | ||
CaptureKind::Weak => { | ||
let err_msg = format!("failed to upgrade `{}`", alias.to_string()); | ||
quote! { | ||
let #alias = match #crate_ident::clone::Upgrade::upgrade(&#alias) { | ||
Some(val) => val, | ||
None => panic!(#err_msg) | ||
}; | ||
} | ||
} | ||
CaptureKind::WeakAllowNone => quote! { | ||
let #alias = #crate_ident::clone::Upgrade::upgrade(&#alias); | ||
}, | ||
_ => Default::default(), | ||
} | ||
} | ||
} | ||
|
||
impl syn::parse::Parse for CaptureKind { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
input.parse::<Token![@]>()?; | ||
let mut idents = vec![input.parse::<syn::Ident>()?]; | ||
while input.peek(Token![-]) { | ||
input.parse::<Token![-]>()?; | ||
idents.push(input.parse()?); | ||
} | ||
let keyword = idents | ||
.into_iter() | ||
.map(|i| i.to_string()) | ||
.collect::<Vec<_>>() | ||
.join("-"); | ||
Ok(match keyword.as_str() { | ||
"strong" => CaptureKind::Strong, | ||
"weak" => CaptureKind::Weak, | ||
"weak-allow-none" => CaptureKind::WeakAllowNone, | ||
k => panic!( | ||
"Unknown keyword `{}`, only `weak`, `weak-allow-none` and `strong` are allowed", | ||
k, | ||
), | ||
}) | ||
} | ||
} | ||
|
||
impl syn::parse::Parse for Capture { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
let kind = input.parse()?; | ||
let mut name = TokenStream::new(); | ||
name.append(input.parse::<syn::Ident>()?); | ||
while input.peek(Token![.]) { | ||
input.parse::<Token![.]>()?; | ||
name.append(proc_macro2::Punct::new('.', proc_macro2::Spacing::Alone)); | ||
name.append(input.parse::<syn::Ident>()?); | ||
} | ||
if name.to_string() == "self" { | ||
panic!( | ||
"Can't use `self` as variable name. Try storing it in a temporary variable or \ | ||
rename it using `as`." | ||
); | ||
} | ||
let alias = if input.peek(Token![as]) { | ||
input.parse::<Token![as]>()?; | ||
input.parse()? | ||
} else { | ||
None | ||
}; | ||
assert!( | ||
!name.to_string().contains('.') || alias.is_some(), | ||
"`{}`: Field accesses are not allowed as is, you must rename it!", | ||
name | ||
); | ||
Ok(Capture { name, alias, kind }) | ||
} | ||
} | ||
|
||
struct Closure { | ||
captures: Vec<Capture>, | ||
args: Vec<Ident>, | ||
closure: syn::ExprClosure, | ||
constructor: &'static str | ||
} | ||
|
||
impl syn::parse::Parse for Closure { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
let mut captures = vec![]; | ||
if input.peek(Token![@]) { | ||
loop { | ||
captures.push(input.parse()?); | ||
if input.peek(Token![,]) { | ||
input.parse::<Token![,]>()?; | ||
if !input.peek(Token![@]) { | ||
break; | ||
} | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
if !captures.is_empty() { | ||
input.parse::<Token![=>]>()?; | ||
} | ||
let mut closure = input.parse::<syn::ExprClosure>()?; | ||
if closure.asyncness.is_some() { | ||
panic!("Async closure not allowed"); | ||
} | ||
if !captures.is_empty() && closure.capture.is_none() { | ||
panic!( | ||
"Closure with captures needs to be \"moved\" so please add `move` before closure" | ||
) | ||
} | ||
let args = closure | ||
.inputs | ||
.iter() | ||
.enumerate() | ||
.map(|(i, _)| Ident::new(&format!("____value{}", i), Span::call_site())) | ||
.collect(); | ||
closure.capture = None; | ||
Ok(Closure { | ||
captures, | ||
args, | ||
closure, | ||
constructor: "new" | ||
}) | ||
} | ||
} | ||
|
||
impl ToTokens for Closure { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
let closure_ident = Ident::new("____closure", Span::call_site()); | ||
let values_ident = Ident::new("____values", Span::call_site()); | ||
let crate_ident = crate_ident_new(); | ||
|
||
let outer_before = self | ||
.captures | ||
.iter() | ||
.map(|c| c.outer_before_tokens(&crate_ident)); | ||
let inner_before = self | ||
.captures | ||
.iter() | ||
.map(|c| c.inner_before_tokens(&crate_ident)); | ||
let outer_after = self | ||
.captures | ||
.iter() | ||
.map(|c| c.outer_after_tokens(&crate_ident, &closure_ident)); | ||
|
||
let arg_values = self.args.iter().enumerate().map(|(index, arg)| { | ||
let err_msg = format!("Wrong type for argument {}: {{:?}}", index); | ||
quote! { | ||
let #arg = ::core::result::Result::unwrap_or_else( | ||
#crate_ident::Value::get(&#values_ident[#index]), | ||
|e| panic!(#err_msg, e), | ||
); | ||
} | ||
}); | ||
let arg_names = &self.args; | ||
let closure = &self.closure; | ||
let constructor = Ident::new(self.constructor, Span::call_site()); | ||
|
||
tokens.extend(quote! { | ||
{ | ||
let #closure_ident = { | ||
#(#outer_before)* | ||
#crate_ident::closure::Closure::#constructor(move |#values_ident| { | ||
#(#inner_before)* | ||
#(#arg_values)* | ||
#crate_ident::closure::ToOptionValue::to_option_value( | ||
&(#closure)(#(#arg_names),*) | ||
) | ||
}) | ||
}; | ||
#(#outer_after)* | ||
#closure_ident | ||
} | ||
}); | ||
} | ||
} | ||
|
||
pub(crate) fn gclosure_inner(input: proc_macro::TokenStream, constructor: &'static str) -> proc_macro::TokenStream { | ||
let mut closure = syn::parse_macro_input!(input as Closure); | ||
closure.constructor = constructor; | ||
closure.into_token_stream().into() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.