Skip to content

Commit

Permalink
fix(macros): 🐛 miss capture states used in embed closure
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo committed Aug 17, 2023
1 parent f1405a7 commit 6f346dc
Show file tree
Hide file tree
Showing 8 changed files with 380 additions and 186 deletions.
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
21 changes: 17 additions & 4 deletions macros/src/declare_obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Macro>,
}
enum ObjNode<'a> {
Expand All @@ -32,7 +32,20 @@ enum ObjNode<'a> {
impl<'a> DeclareObj<'a> {
pub fn from_literal(mac: &'a StructLiteral) -> Result<Self, TokenStream> {
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(),
Expand All @@ -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)
}
Expand All @@ -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.")
Expand Down
29 changes: 14 additions & 15 deletions macros/src/fn_widget_macro.rs
Original file line number Diff line number Diff line change
@@ -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<Stmt>,
}
pub struct FnWidgetMacro(Vec<Stmt>);

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<Self> {
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);
Expand Down
83 changes: 30 additions & 53 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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()
}
Expand All @@ -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()
}
Expand Down Expand Up @@ -134,29 +144,20 @@ 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
/// a expression. Its syntax is extended from rust syntax, you can use `@` and
/// `$` 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.
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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 `$`.
Expand Down
44 changes: 20 additions & 24 deletions macros/src/pipe_macro.rs
Original file line number Diff line number Diff line change
@@ -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<Stmt>);
pub(crate) struct PipeMacro {
refs: DollarRefs,
expr: Vec<Stmt>,
}

impl Parse for PipeExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
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<Stmt>)> {
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<Self> { 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;

Expand Down
Loading

0 comments on commit 6f346dc

Please sign in to comment.