diff --git a/core/src/lib.rs b/core/src/lib.rs index b0d1394c7..660bb86e9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -61,6 +61,7 @@ pub mod prelude { pub use ribir_macros::{ ctx, fn_widget, include_svg, orphan_partial_state, partial_state, pipe, rdl, set_build_ctx, watch, widget, Declare, Declare2, Lerp, MultiChild, SingleChild, Template, _dollar_ಠ_ಠ, + ribir_expanded_ಠ_ಠ, }; #[doc(no_inline)] pub use ribir_painter::*; diff --git a/macros/src/declare_obj.rs b/macros/src/declare_obj.rs index 47cf5de54..2cf5dee62 100644 --- a/macros/src/declare_obj.rs +++ b/macros/src/declare_obj.rs @@ -17,7 +17,7 @@ use syn::{ pub struct DeclareObj<'a> { this: ObjNode<'a>, span: Span, - builtin: ahash::HashMap<&'static str, SmallVec<[&'a DeclareField; 1]>>, + builtin: Vec<(&'static str, SmallVec<[&'a DeclareField; 1]>)>, children: &'a Vec, } enum ObjNode<'a> { @@ -32,7 +32,20 @@ enum ObjNode<'a> { impl<'a> DeclareObj<'a> { pub fn from_literal(mac: &'a StructLiteral) -> Result { let StructLiteral { parent, brace, fields, children } = mac; - let mut builtin: ahash::HashMap<_, SmallVec<[&DeclareField; 1]>> = <_>::default(); + let mut builtin = vec![]; + + fn get_builtin_obj<'a, 'b>( + ty_name: &'static str, + builtins: &'a mut Vec<(&'static str, SmallVec<[&'b DeclareField; 1]>)>, + ) -> &'a mut SmallVec<[&'b DeclareField; 1]> { + if let Some(idx) = builtins.iter_mut().position(|(ty, _)| *ty == ty_name) { + &mut builtins[idx].1 + } else { + builtins.push((ty_name, SmallVec::default())); + &mut builtins.last_mut().unwrap().1 + } + } + let span = match parent { RdlParent::Type(ty) => ty.span(), RdlParent::Var(name) => name.span(), @@ -47,7 +60,7 @@ impl<'a> DeclareObj<'a> { .get(f.member.to_string().as_str()) .filter(|builtin_ty| !ty.is_ident(builtin_ty)) { - builtin.entry(*ty).or_default().push(f); + get_builtin_obj(ty, &mut builtin).push(f); } else { self_fields.push(f) } @@ -57,7 +70,7 @@ impl<'a> DeclareObj<'a> { RdlParent::Var(name) => { for f in fields { if let Some(ty) = WIDGET_OF_BUILTIN_FIELD.get(f.member.to_string().as_str()) { - builtin.entry(*ty).or_default().push(f); + get_builtin_obj(ty, &mut builtin).push(f); } else { return Err(quote_spanned! { f.span() => compile_error!("Not allow to declare a field of a variable parent.") diff --git a/macros/src/fn_widget_macro.rs b/macros/src/fn_widget_macro.rs index 20159c9d5..ac1a1c80e 100644 --- a/macros/src/fn_widget_macro.rs +++ b/macros/src/fn_widget_macro.rs @@ -1,28 +1,27 @@ +use crate::{ok, pipe_macro::BodyExpr, symbol_process::symbol_to_macro}; +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{ - fold::Fold, - parse::{Parse, ParseStream}, - Stmt, -}; +use syn::{fold::Fold, parse_macro_input, Stmt}; use crate::symbol_process::DollarRefs; -pub struct FnWidgetMacro { - stmts: Vec, -} +pub struct FnWidgetMacro(Vec); + +impl FnWidgetMacro { + pub(crate) fn gen_code(input: TokenStream, outside: Option<&mut DollarRefs>) -> TokenStream1 { + let input = ok!(symbol_to_macro(input.into())); + let body = parse_macro_input!(input as BodyExpr); + let mut refs = DollarRefs::new(outside.map_or(0, |o| o.in_capture_level())); + let stmts = body.0.into_iter().map(|s| refs.fold_stmt(s)).collect(); -impl Parse for FnWidgetMacro { - fn parse(input: ParseStream) -> syn::Result { - let stmts = syn::Block::parse_within(input)?; - let mut refs = DollarRefs::default(); - let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)).collect(); - Ok(Self { stmts }) + FnWidgetMacro(stmts).to_token_stream().into() } } impl ToTokens for FnWidgetMacro { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let Self { stmts } = self; + let Self(stmts) = self; quote! { FnWidget::new(move |ctx: &BuildCtx| { set_build_ctx!(ctx); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 0f951992f..84b35fb51 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -10,7 +10,7 @@ mod widget_macro; use fn_widget_macro::FnWidgetMacro; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use symbol_process::symbol_to_macro; +use symbol_process::{symbol_to_macro, DollarRefs}; use syn::{parse_macro_input, DeriveInput}; use widget_macro::gen_widget_macro; mod child_template; @@ -21,7 +21,7 @@ mod split_state_macro; mod watch_macro; pub(crate) use rdl_macro::*; -use crate::pipe_macro::PipeExpr; +use crate::pipe_macro::PipeMacro; use crate::watch_macro::WatchMacro; use split_state_macro::SplitStateMacro; pub(crate) mod declare_obj; @@ -35,13 +35,23 @@ pub(crate) const ASSIGN_WATCH_MACRO_NAME: &str = "assign_watch"; pub(crate) const LET_WATCH_MACRO_NAME: &str = "let_watch"; pub(crate) const PROP_MACRO_NAME: &str = "prop"; +macro_rules! ok { + ($e: expr) => { + match $e { + Ok(ok) => ok, + Err(err) => return err.into(), + } + }; +} +pub(crate) use ok; + #[proc_macro_derive(SingleChild)] pub fn single_marco_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let name = input.ident; quote! { - impl #impl_generics SingleChild for #name #ty_generics #where_clause {} + impl #impl_generics SingleChild for #name #ty_generics #where_clause {} } .into() } @@ -52,7 +62,7 @@ pub fn multi_macro_derive(input: TokenStream) -> TokenStream { let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let name = input.ident; quote! { - impl #impl_generics MultiChild for #name #ty_generics #where_clause {} + impl #impl_generics MultiChild for #name #ty_generics #where_clause {} } .into() } @@ -134,13 +144,7 @@ pub fn widget(input: TokenStream) -> TokenStream { gen_widget_macro(input, None) /// like: `let row = rdl!{ Widget::new(Void) };` #[proc_macro] pub fn rdl(input: TokenStream) -> TokenStream { - symbol_to_macro(input).map_or_else( - |err| err, - |input| { - let declare = parse_macro_input! { input as RdlBody }; - declare.to_token_stream().into() - }, - ) + RdlMacro::gen_code(input.into(), &mut DollarRefs::default()) } /// The `fn_widget` is a macro that create a widget from a function widget from @@ -148,15 +152,12 @@ pub fn rdl(input: TokenStream) -> TokenStream { /// `$` in the expression, the `@` is a short hand of `rdl` macro, and `$name` /// use to expression a state reference of `name`. #[proc_macro] -pub fn fn_widget(input: TokenStream) -> TokenStream { - symbol_to_macro(input).map_or_else( - |err| err, - |input| { - let widget_macro = parse_macro_input!(input as FnWidgetMacro); - widget_macro.to_token_stream().into() - }, - ) -} +pub fn fn_widget(input: TokenStream) -> TokenStream { FnWidgetMacro::gen_code(input.into(), None) } + +/// This macro just return the input token stream. It's do nothing but help +/// `ribir` mark that a macro has been expanded. +#[proc_macro] +pub fn ribir_expanded_ಠ_ಠ(input: TokenStream) -> TokenStream { input } /// set the `BuildCtx` to a special variable `_ctx_ಠ_ಠ`, so the user can use /// `ctx!` to access it. @@ -181,28 +182,12 @@ pub fn ctx(input: TokenStream) -> TokenStream { /// expression modify. Use the `$` mark the state reference and auto subscribe /// to its modify. #[proc_macro] -pub fn pipe(input: TokenStream) -> TokenStream { - symbol_to_macro(input).map_or_else( - |err| err, - |input| { - let expr = parse_macro_input! { input as PipeExpr }; - expr.to_token_stream().into() - }, - ) -} +pub fn pipe(input: TokenStream) -> TokenStream { PipeMacro::gen_code(input.into(), None) } /// `watch!` macro use to convert a expression to a `Observable` stream. Use the /// `$` mark the state reference and auto subscribe to its modify. #[proc_macro] -pub fn watch(input: TokenStream) -> TokenStream { - symbol_to_macro(input).map_or_else( - |err| err, - |input| { - let expr = parse_macro_input! { input as WatchMacro }; - expr.to_token_stream().into() - }, - ) -} +pub fn watch(input: TokenStream) -> TokenStream { WatchMacro::gen_code(input.into(), None) } /// macro split state from another state as a new state. For example, /// `partial_state!($label.visible, Visibility -> bool );` will return a state @@ -212,13 +197,9 @@ pub fn watch(input: TokenStream) -> TokenStream { /// the origin state. #[proc_macro] pub fn partial_state(input: TokenStream) -> TokenStream { - symbol_to_macro(input).map_or_else( - |err| err, - |input| { - let split_state = parse_macro_input! { input as SplitStateMacro }; - split_state.to_token_stream().into() - }, - ) + let input = ok!(symbol_to_macro(input)); + let split_state = parse_macro_input! { input as SplitStateMacro }; + split_state.to_token_stream().into() } /// macro split a state from another state as a new state. For example, @@ -230,14 +211,10 @@ pub fn partial_state(input: TokenStream) -> TokenStream { /// split from the origin state. #[proc_macro] pub fn orphan_partial_state(input: TokenStream) -> TokenStream { - symbol_to_macro(input).map_or_else( - |err| err, - |input| { - let mut split_state = parse_macro_input! { input as SplitStateMacro }; - split_state.orphan_split = true; - split_state.to_token_stream().into() - }, - ) + let input = ok!(symbol_to_macro(input)); + let mut split_state = parse_macro_input! { input as SplitStateMacro }; + split_state.orphan_split = true; + split_state.to_token_stream().into() } /// The macro to use a state as its StateRef. Transplanted from the `$`. diff --git a/macros/src/pipe_macro.rs b/macros/src/pipe_macro.rs index b4fba7708..bc1464839 100644 --- a/macros/src/pipe_macro.rs +++ b/macros/src/pipe_macro.rs @@ -1,42 +1,38 @@ -use crate::symbol_process::DollarRefs; +use crate::symbol_process::{fold_body_in_capture_and_require_refs, DollarRefs}; +use crate::{ok, symbol_process::symbol_to_macro}; +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ - fold::Fold, parse::{Parse, ParseStream}, + parse_macro_input, + spanned::Spanned, Stmt, }; -pub(crate) struct PipeExpr { +pub(crate) struct BodyExpr(pub(crate) Vec); +pub(crate) struct PipeMacro { refs: DollarRefs, expr: Vec, } -impl Parse for PipeExpr { - fn parse(input: ParseStream) -> syn::Result { - let (refs, stmts) = fold_expr_as_in_closure(input)?; - Ok(Self { refs, expr: stmts }) - } -} +impl PipeMacro { + pub fn gen_code(input: TokenStream, outside: Option<&mut DollarRefs>) -> TokenStream1 { + let span = input.span(); + let input = ok!(symbol_to_macro(input.into())); + let expr = parse_macro_input! { input as BodyExpr }; -pub fn fold_expr_as_in_closure(input: ParseStream) -> syn::Result<(DollarRefs, Vec)> { - let mut refs = DollarRefs::default(); - refs.in_capture += 1; - let stmts = syn::Block::parse_within(input)?; - let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)).collect(); - refs.in_capture -= 1; - if refs.is_empty() { - let err = syn::Error::new( - input.span(), - "expression not subscribe anything, it must contain at least one $", - ); - Err(err) - } else { + let (expr, mut refs) = ok!(fold_body_in_capture_and_require_refs(span, expr.0, outside)); refs.dedup(); - Ok((refs, stmts)) + PipeMacro { refs, expr }.to_token_stream().into() } } -impl ToTokens for PipeExpr { +impl Parse for BodyExpr { + fn parse(input: ParseStream) -> syn::Result { Ok(Self(syn::Block::parse_within(input)?)) } +} + +impl ToTokens for PipeMacro { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { refs, expr } = self; diff --git a/macros/src/rdl_macro.rs b/macros/src/rdl_macro.rs index 9a7140796..025baad35 100644 --- a/macros/src/rdl_macro.rs +++ b/macros/src/rdl_macro.rs @@ -1,23 +1,24 @@ +use crate::{ + declare_obj::DeclareObj, + ok, + symbol_process::{kw, symbol_to_macro, DollarRefs}, +}; +use proc_macro::TokenStream as TokenStream1; use proc_macro2::{Span, TokenStream}; -use quote::{quote_spanned, ToTokens}; +use quote::{quote, quote_spanned, ToTokens}; use std::collections::HashSet; use syn::{ braced, fold::Fold, parse::{Parse, ParseBuffer, ParseStream}, - parse_quote, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::{At, Bang, Brace, Colon, Comma, Dollar}, Expr, Ident, Macro, Path, Result as SynResult, Stmt, }; -use crate::{ - declare_obj::DeclareObj, - symbol_process::{kw, DollarRefs}, -}; - -pub enum RdlBody { +pub enum RdlMacro { Literal(StructLiteral), /// Declare an expression as a object, like `rdl! { Widget::new(...) }` ExprObj { @@ -55,17 +56,44 @@ pub struct DeclareField { pub value: Expr, } -impl Parse for RdlBody { +impl RdlMacro { + pub fn gen_code(input: TokenStream, refs: &mut DollarRefs) -> TokenStream1 { + let input = ok!(symbol_to_macro(input.into())); + + match parse_macro_input! { input as RdlMacro } { + RdlMacro::Literal(mut l) => { + let fields = l.fields.into_iter().map(|mut f: DeclareField| { + f.value = refs.fold_expr(f.value); + f + }); + l.fields = fields.collect(); + l.children = l.children.into_iter().map(|m| refs.fold_macro(m)).collect(); + + let obj = ok!(DeclareObj::from_literal(&l)); + obj.to_token_stream().into() + } + RdlMacro::ExprObj { span, stmts } => { + let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)); + if stmts.len() > 1 { + quote_spanned! { span => { #(#stmts)* }}.into() + } else { + quote! { #(#stmts)* }.into() + } + } + } + } +} + +impl Parse for RdlMacro { fn parse(input: ParseStream) -> SynResult { let fork = input.fork(); if fork.parse::().is_ok() && fork.peek(Brace) { - Ok(RdlBody::Literal(input.parse()?)) + Ok(RdlMacro::Literal(input.parse()?)) } else { - let span = input.span(); - let stmts = syn::Block::parse_within(input)?; - let mut refs = DollarRefs::default(); - let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)).collect(); - Ok(RdlBody::ExprObj { span, stmts }) + Ok(RdlMacro::ExprObj { + span: input.span(), + stmts: syn::Block::parse_within(input)?, + }) } } } @@ -131,34 +159,13 @@ impl Parse for DeclareField { let value = if colon_tk.is_none() { parse_quote!(#member) } else { - let mut refs = DollarRefs::default(); - refs.fold_expr(input.parse()?) + input.parse()? }; Ok(DeclareField { member, colon_tk, value }) } } -impl ToTokens for RdlBody { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - RdlBody::Literal(l) => match DeclareObj::from_literal(l) { - Ok(declare) => declare.to_tokens(tokens), - Err(err) => err.to_tokens(tokens), - }, - RdlBody::ExprObj { span, stmts } => { - if stmts.len() > 1 { - Brace(*span).surround(tokens, |tokens| { - stmts.iter().for_each(|s| s.to_tokens(tokens)); - }) - } else { - stmts.iter().for_each(|s| s.to_tokens(tokens)); - } - } - } - } -} - impl ToTokens for DeclareField { fn to_tokens(&self, tokens: &mut TokenStream) { let DeclareField { member, value, .. } = self; diff --git a/macros/src/symbol_process.rs b/macros/src/symbol_process.rs index 51519bb9e..f66dae8e9 100644 --- a/macros/src/symbol_process.rs +++ b/macros/src/symbol_process.rs @@ -1,22 +1,29 @@ -use crate::widget_macro::{ - ribir_suffix_variable, WIDGET_OF_BUILTIN_FIELD, WIDGET_OF_BUILTIN_METHOD, +use crate::fn_widget_macro::FnWidgetMacro; +use crate::pipe_macro::PipeMacro; +use crate::rdl_macro::RdlMacro; +use crate::{ + watch_macro::WatchMacro, + widget_macro::{ribir_suffix_variable, WIDGET_OF_BUILTIN_FIELD, WIDGET_OF_BUILTIN_METHOD}, }; use inflector::Inflector; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use smallvec::SmallVec; +use syn::parse_quote_spanned; use syn::{ fold::Fold, parse::{Parse, ParseStream}, - parse_quote, spanned::Spanned, token::Dollar, - Expr, ExprField, ExprMethodCall, Macro, Member, + Expr, ExprField, ExprMethodCall, Macro, Member, Stmt, }; pub const KW_DOLLAR_STR: &str = "_dollar_ಠ_ಠ"; pub const KW_CTX: &str = "ctx"; pub const KW_RDL: &str = "rdl"; +pub const KW_PIPE: &str = "pipe"; +pub const KW_WATCH: &str = "watch"; +pub const KW_FN_WIDGET: &str = "fn_widget"; pub use tokens_pre_process::*; pub mod kw { @@ -24,18 +31,27 @@ pub mod kw { syn::custom_keyword!(rdl); } +#[derive(Hash, PartialEq, Eq, Debug, Clone)] +pub struct BuiltinInfo { + host: Ident, + member: Ident, +} + #[derive(Hash, PartialEq, Eq, Debug, Clone)] pub struct DollarRef { pub name: Ident, - pub builtin_shadow: Option, + pub builtin: Option, } -#[derive(Default)] +#[derive(Debug)] pub struct DollarRefs { refs: SmallVec<[DollarRef; 1]>, ctx_used: bool, - pub in_capture: usize, + in_capture: usize, + variable_stacks: Vec>, } +pub struct StackGuard<'a>(&'a mut DollarRefs); + mod tokens_pre_process { use proc_macro::{TokenTree, *}; @@ -158,6 +174,67 @@ mod tokens_pre_process { } impl Fold for DollarRefs { + fn fold_block(&mut self, i: syn::Block) -> syn::Block { + let mut this = self.push_stack(); + syn::fold::fold_block(&mut *this, i) + } + + fn fold_item_const(&mut self, i: syn::ItemConst) -> syn::ItemConst { + self.new_local_var(&i.ident); + syn::fold::fold_item_const(self, i) + } + + fn fold_local(&mut self, mut i: syn::Local) -> syn::Local { + // we fold right expression first, then fold pattern, because the `=` is a + // right operator. + i.init = i + .init + .map(|(assign, e)| (assign, Box::new(self.fold_expr(*e)))); + i.pat = self.fold_pat(i.pat); + + i + } + + fn fold_expr_block(&mut self, i: syn::ExprBlock) -> syn::ExprBlock { + let mut this = self.push_stack(); + syn::fold::fold_expr_block(&mut *this, i) + } + + fn fold_expr_for_loop(&mut self, i: syn::ExprForLoop) -> syn::ExprForLoop { + let mut this = self.push_stack(); + syn::fold::fold_expr_for_loop(&mut *this, i) + } + + fn fold_expr_loop(&mut self, i: syn::ExprLoop) -> syn::ExprLoop { + let mut this = self.push_stack(); + syn::fold::fold_expr_loop(&mut *this, i) + } + + fn fold_expr_if(&mut self, i: syn::ExprIf) -> syn::ExprIf { + let mut this = self.push_stack(); + syn::fold::fold_expr_if(&mut *this, i) + } + + fn fold_arm(&mut self, i: syn::Arm) -> syn::Arm { + let mut this = self.push_stack(); + syn::fold::fold_arm(&mut *this, i) + } + + fn fold_expr_unsafe(&mut self, i: syn::ExprUnsafe) -> syn::ExprUnsafe { + let mut this = self.push_stack(); + syn::fold::fold_expr_unsafe(&mut *this, i) + } + + fn fold_expr_while(&mut self, i: syn::ExprWhile) -> syn::ExprWhile { + let mut this = self.push_stack(); + syn::fold::fold_expr_while(&mut *this, i) + } + + fn fold_pat_ident(&mut self, i: syn::PatIdent) -> syn::PatIdent { + self.new_local_var(&i.ident); + syn::fold::fold_pat_ident(self, i) + } + fn fold_expr_field(&mut self, mut i: ExprField) -> ExprField { let ExprField { base, member, .. } = &mut i; if let Member::Named(member) = member { @@ -180,21 +257,40 @@ impl Fold for DollarRefs { fn fold_macro(&mut self, mut mac: Macro) -> Macro { if let Some(DollarMacro { name, .. }) = parse_clean_dollar_macro(&mac) { mac.tokens = name.to_token_stream(); - self.refs.push(DollarRef { name, builtin_shadow: None }); - mac + if !self.is_local_variable(&name) { + self.refs.push(DollarRef { name, builtin: None }); + } + } else if mac.path.is_ident(KW_WATCH) { + mac.tokens = WatchMacro::gen_code(mac.tokens, Some(self)).into(); + mac.path = parse_quote_spanned! { mac.path.span() => ribir_expanded_ಠ_ಠ }; + } else if mac.path.is_ident(KW_PIPE) { + self.ctx_used = true; + mac.tokens = PipeMacro::gen_code(mac.tokens, Some(self)).into(); + mac.path = parse_quote_spanned! { mac.path.span() => ribir_expanded_ಠ_ಠ }; + } else if mac.path.is_ident(KW_RDL) { + self.ctx_used = true; + mac.tokens = RdlMacro::gen_code(mac.tokens, self).into(); + mac.path = parse_quote_spanned! { mac.path.span() => ribir_expanded_ಠ_ಠ }; + } else if mac.path.is_ident(KW_FN_WIDGET) { + mac.tokens = FnWidgetMacro::gen_code(mac.tokens, Some(self)).into(); + mac.path = parse_quote_spanned! { mac.path.span() => ribir_expanded_ಠ_ಠ }; + } else if mac.path.is_ident(KW_CTX) { + self.ctx_used = true; } else { - self.ctx_used = mac.path.is_ident(KW_RDL) || mac.path.is_ident(KW_CTX); - syn::fold::fold_macro(self, mac) + mac = syn::fold::fold_macro(self, mac); } + mac } - fn fold_expr(&mut self, i: Expr) -> Expr { match i { Expr::Closure(c) if c.capture.is_some() => { - let mut closure_refs = DollarRefs::default(); + let mut closure_refs = DollarRefs { + in_capture: self.in_capture, + ..Default::default() + }; + closure_refs.in_capture += 1; let mut c = closure_refs.fold_expr_closure(c); - closure_refs.in_capture -= 1; if !closure_refs.is_empty() || closure_refs.ctx_used { closure_refs.dedup(); @@ -212,13 +308,16 @@ impl Fold for DollarRefs { .ctx_used .then(|| quote_spanned! { c.span() => let _ctx_handle = ctx!().handle(); }); - // todo: seems need merge `closure_refs` into `self`. - - Expr::Verbatim(quote_spanned!(c.span() => { + let expr = Expr::Verbatim(quote_spanned!(c.span() => { #closure_refs #handle #c - })) + })); + + self.merge(&mut closure_refs); + closure_refs.in_capture -= 1; + + expr } else { Expr::Closure(c) } @@ -228,25 +327,56 @@ impl Fold for DollarRefs { } } -impl ToTokens for DollarRef { - fn to_tokens(&self, tokens: &mut TokenStream) { - let DollarRef { name, builtin_shadow: shadow } = self; - if let Some(shadow) = shadow { - quote_spanned! { shadow.span() => let mut #name = #shadow.clone();}.to_tokens(tokens); - } else { - quote_spanned! { shadow.span() => let mut #name = #name.clone();}.to_tokens(tokens); - } - } -} impl ToTokens for DollarRefs { fn to_tokens(&self, tokens: &mut TokenStream) { - for dollar_ref in &self.refs { - dollar_ref.to_tokens(tokens); + for DollarRef { name, builtin } in &self.refs { + match builtin { + Some(builtin) => { + let BuiltinInfo { host, member } = builtin; + quote_spanned! { name.span() => let mut #name = #host.#member(ctx!()).clone();} + .to_tokens(tokens); + } + _ => { + quote_spanned! { name.span() => let mut #name = #name.clone();}.to_tokens(tokens); + } + } } } } impl DollarRefs { + pub fn new(in_capture: usize) -> Self { Self { in_capture, ..Default::default() } } + + pub fn in_capture_level(&self) -> usize { self.in_capture } + + pub fn push_stack(&mut self) -> StackGuard<'_> { + self.variable_stacks.push(vec![]); + StackGuard(self) + } + + pub fn merge(&mut self, other: &mut Self) { + for r in other.refs.iter_mut() { + if let Some(builtin) = r.builtin.as_mut() { + if !self.is_local_variable(&builtin.host) { + if r.name == "host_has_focus_ಠ_ಠ" { + println!( + "make {} : `host_has_focus_ಠ_ಠ` as not builtin widget", + builtin.host + ); + + println!("local variables: {:?}", self.variable_stacks); + } + self.refs.push(r.clone()); + r.builtin.take(); + } + } else if !self.is_local_variable(&r.name) { + self.refs.push(r.clone()) + } + } + + self.ctx_used |= other.ctx_used; + } + pub fn used_ctx(&self) -> bool { self.ctx_used } pub fn dedup(&mut self) { @@ -258,8 +388,8 @@ impl DollarRefs { match self.len() { 0 => quote! {}, 1 => { - let DollarRef { name, builtin_shadow: value } = &self.refs[0]; - quote_spanned! { value.span() => #name.modifies() } + let DollarRef { name, .. } = &self.refs[0]; + quote_spanned! { name.span() => #name.modifies() } } _ => { let upstream = self.iter().map(|DollarRef { name, .. }| { @@ -284,25 +414,43 @@ impl DollarRefs { e => e, }; + // When a builtin widget captured by a `move |_| {...}` closure, we need split + // the builtin widget from the `FatObj` so we only capture the builtin part that + // we used. + let Expr::Macro(m) = e else { return None }; let DollarMacro { name: host, .. } = parse_clean_dollar_macro(&m.mac)?; - let builtin_name = ribir_suffix_variable(&host, builtin_member); + let name = ribir_suffix_variable(&host, builtin_member); let builtin_member = Ident::new(builtin_member, host.span()); - // When a builtin widget captured by a `move |_| {...}` closure, we need split - // the builtin widget from the `FatObj` so we only capture the used builtin - // part. - m.mac.tokens = if self.in_capture > 0 { - builtin_name.to_token_stream() + // if used in embedded closure, we directly use the builtin variable, the + // variable that the closure capture is already a separate builtin variable. + let is_local_declare = self.is_local_variable(&host); + + m.mac.tokens = if !is_local_declare && self.in_capture > 0 { + name.to_token_stream() } else { quote_spanned! { host.span() => #host.#builtin_member(ctx!()) } }; - self.refs.push(DollarRef { - name: builtin_name, - builtin_shadow: Some(parse_quote! { #host.#builtin_member(ctx!()) }), - }); + + if !is_local_declare { + let builtin = Some(BuiltinInfo { host, member: builtin_member }); + self.refs.push(DollarRef { name, builtin }); + } + self.last() } + + fn new_local_var(&mut self, name: &Ident) { + self.variable_stacks.last_mut().unwrap().push(name.clone()) + } + + fn is_local_variable(&self, name: &Ident) -> bool { + self + .variable_stacks + .iter() + .any(|stack| stack.contains(name)) + } } fn parse_clean_dollar_macro(mac: &Macro) -> Option { @@ -332,3 +480,48 @@ impl Parse for DollarMacro { }) } } + +impl<'a> std::ops::Deref for StackGuard<'a> { + type Target = DollarRefs; + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl<'a> std::ops::DerefMut for StackGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl<'a> Drop for StackGuard<'a> { + fn drop(&mut self) { self.0.variable_stacks.pop(); } +} + +impl Default for DollarRefs { + fn default() -> Self { + Self { + refs: SmallVec::new(), + ctx_used: false, + in_capture: 0, + variable_stacks: vec![vec![]], + } + } +} + +pub fn fold_body_in_capture_and_require_refs( + span: Span, + stmts: Vec, + outside: Option<&mut DollarRefs>, +) -> Result<(Vec, DollarRefs), TokenStream> { + let in_capture = outside.as_ref().map_or(0, |o| o.in_capture) + 1; + let mut refs = DollarRefs::new(in_capture); + + let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)).collect(); + if refs.is_empty() { + Err(quote_spanned!(span => + compile_error!("expression not subscribe anything, it must contain at least one $") + )) + } else { + if let Some(outside) = outside { + outside.merge(&mut refs); + } + Ok((stmts, refs)) + } +} diff --git a/macros/src/watch_macro.rs b/macros/src/watch_macro.rs index 2c4e27b9c..485499c33 100644 --- a/macros/src/watch_macro.rs +++ b/macros/src/watch_macro.rs @@ -1,25 +1,33 @@ -use crate::{pipe_macro::fold_expr_as_in_closure, symbol_process::DollarRefs}; -use quote::{quote, ToTokens}; -use syn::{ - parse::{Parse, ParseStream}, - Stmt, +use crate::{ + ok, + pipe_macro::BodyExpr, + symbol_process::{fold_body_in_capture_and_require_refs, symbol_to_macro, DollarRefs}, }; +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, spanned::Spanned, Stmt}; pub(crate) struct WatchMacro { refs: DollarRefs, expr: Vec, } -impl Parse for WatchMacro { - fn parse(input: ParseStream) -> syn::Result { - let (refs, stmts) = fold_expr_as_in_closure(input)?; - Ok(Self { refs, expr: stmts }) +impl WatchMacro { + pub fn gen_code(input: TokenStream, outside: Option<&mut DollarRefs>) -> TokenStream1 { + let span = input.span(); + let input = ok!(symbol_to_macro(input.into())); + let expr = parse_macro_input! { input as BodyExpr }; + + let (expr, mut refs) = ok!(fold_body_in_capture_and_require_refs(span, expr.0, outside)); + refs.dedup(); + WatchMacro { refs, expr }.to_token_stream().into() } } impl ToTokens for WatchMacro { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let Self { refs, expr } = self; + let Self { refs, expr, .. } = self; let upstream = refs.upstream_tokens();