Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix destructuring async props #419

Merged
merged 6 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 116 additions & 23 deletions packages/sycamore-macro/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,16 @@ impl Parse for ComponentFunction {
));
}

if let FnArg::Receiver(arg) = &inputs[0] {
if let FnArg::Typed(t) = &inputs[0] {
if !matches!(&*t.pat, Pat::Ident(_)) {
return Err(syn::Error::new(
t.span(),
"First argument to a component is expected to be a `sycamore::reactive::Scope`",
));
}
} else {
return Err(syn::Error::new(
arg.span(),
inputs[0].span(),
"function components can't accept a receiver",
));
}
Expand Down Expand Up @@ -97,6 +104,95 @@ impl Parse for ComponentFunction {
}
}

struct AsyncCompInputs {
cx: syn::Ident,
sync_input: Punctuated<FnArg, syn::token::Comma>,
async_args: Vec<Expr>,
}

fn async_comp_inputs_from_sig_inputs(
inputs: &Punctuated<FnArg, syn::token::Comma>,
) -> AsyncCompInputs {
let mut sync_input = Punctuated::new();
let mut async_args = Vec::with_capacity(2);

#[inline]
fn pat_ident_arm(
sync_input: &mut Punctuated<FnArg, syn::token::Comma>,
fn_arg: &FnArg,
id: &syn::PatIdent,
) -> syn::Expr {
sync_input.push(fn_arg.clone());
let ident = &id.ident;
parse_quote! { #ident }
}

let mut inputs = inputs.iter();

let cx_fn_arg = inputs.next().unwrap();

let cx = if let FnArg::Typed(t) = cx_fn_arg {
if let Pat::Ident(id) = &*t.pat {
async_args.push(pat_ident_arm(&mut sync_input, cx_fn_arg, id));
id.ident.clone()
} else {
unreachable!("We check in parsing that the first argument is a Ident");
}
} else {
unreachable!("We check in parsing that the first argument is not a receiver");
};

// In parsing we checked that there were two args so we can unwrap here.
let prop_fn_arg = inputs.next().unwrap();
let prop_arg = match prop_fn_arg {
FnArg::Typed(t) => match &*t.pat {
Pat::Ident(id) => pat_ident_arm(&mut sync_input, prop_fn_arg, id),
Pat::Wild(_) => {
sync_input.push(prop_fn_arg.clone());
parse_quote!(())
}
Pat::Struct(pat_struct) => {
// For the sync input we don't want a destructured pattern but just to take a
// `syn::PatType` (i.e. `props: MyPropStruct`) then the inner async function
// signature can have the destructured pattern and it will work correctly
// aslong as we provide our brand new ident that we used in the
// `syn::PatIdent`.
let ident = syn::Ident::new("props", pat_struct.span());
// props are taken by value so no refs or mutability required here
// The destructured pattern can add mutability (if required) even without this
// set.
let pat_ident = syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident,
subpat: None,
};
let pat_type = syn::PatType {
attrs: vec![],
pat: Box::new(Pat::Ident(pat_ident)),
colon_token: Default::default(),
ty: t.ty.clone(),
};

let fn_arg = FnArg::Typed(pat_type);
sync_input.push(fn_arg);
parse_quote! { props }
}
_ => panic!("unexpected pattern!"),
},
FnArg::Receiver(_) => unreachable!(),
};

async_args.push(prop_arg);

AsyncCompInputs {
cx,
async_args,
sync_input,
}
}

impl ToTokens for ComponentFunction {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ComponentFunction { f } = self;
Expand All @@ -108,37 +204,34 @@ impl ToTokens for ComponentFunction {
} = &f;

if sig.asyncness.is_some() {
// When the component function is async then we need to extract out some of the
// function signature (Syn::Signature) so that we can wrap the async function with
// a non-async component.
//
// In order to support the struct destructured pattern for props we alter the existing
// signature for the non-async component so that it is defined as a `Syn::PatType`
// (i.e. props: MyPropStruct) with a new `Syn::Ident` "props". We then use this ident
// again as an argument to the inner async function which has the user defined
// destructured pattern which will work as expected.
//
// Note: that the change to the signature is not semantically different to a would be caller.
let inputs = &sig.inputs;
let args: Vec<Expr> = inputs
.iter()
.map(|x| match x {
FnArg::Typed(t) => match &*t.pat {
Pat::Ident(id) => {
let id = &id.ident;
parse_quote! { #id }
}
Pat::Wild(_) => parse_quote!(()),
_ => panic!("unexpected pattern"), // TODO
},
FnArg::Receiver(_) => unreachable!(),
})
.collect::<Vec<_>>();
let AsyncCompInputs {
cx,
sync_input,
async_args: args,
} = async_comp_inputs_from_sig_inputs(inputs);

let non_async_sig = Signature {
asyncness: None,
inputs: sync_input,
..sig.clone()
};
let inner_ident = format_ident!("{}_inner", sig.ident);
let inner_sig = Signature {
ident: inner_ident.clone(),
..sig.clone()
};
let cx = match inputs.first().unwrap() {
FnArg::Typed(t) => match &*t.pat {
Pat::Ident(id) => &id.ident,
_ => unreachable!(),
},
FnArg::Receiver(_) => unreachable!(),
};
tokens.extend(quote! {
#[allow(non_snake_case)]
#(#attrs)*
Expand Down
14 changes: 14 additions & 0 deletions packages/sycamore-macro/tests/view/component-pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ pub fn ComponentWithChildren<'a, G: Html>(cx: Scope<'a>, prop: PropWithChildren<
prop.children.call(cx)
}

#[component]
pub fn AsyncComponentWithPropDestructuring<'a, G: Html>(
mc1098 marked this conversation as resolved.
Show resolved Hide resolved
cx: Scope<'a>,
PropWithChildren { children }: PropWithChildren<'a, G>,
) -> View<G> {
children.call(cx)
}

#[component]
pub fn Component<G: Html>(cx: Scope) -> View<G> {
view! { cx,
Expand Down Expand Up @@ -49,6 +57,12 @@ fn compile_pass<G: Html>() {
Component {}
}
};

let _: View<G> = view! { cx,
AsyncComponentWithPropDestructuring {
Component {}
}
};
});
}

Expand Down