-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Function Components & Hooks V2 #2401
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
Changes from 36 commits
c1ed876
863fa92
22d91f6
d940a7d
572becb
7dc8373
7e8bcdd
2664141
aac78b1
1e5c373
5f086e8
a87b2c7
62396f0
72518e2
38e06b1
19af0b3
49454cd
eb85f47
4f0260c
344ae10
ffca655
7b8978b
15585ff
3dbcdac
f07c648
7118b19
171e085
2f3856a
2b1d6f1
ac765cb
9c300ed
76da3f6
541a945
80dc7e2
347623b
e974dff
7160c18
aa2e803
1799102
acc87d1
00a653a
c861231
e82b4ec
935a672
806f618
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| use proc_macro2::Span; | ||
| use proc_macro_error::emit_error; | ||
| use std::sync::{Arc, Mutex}; | ||
| use syn::spanned::Spanned; | ||
| use syn::visit_mut::VisitMut; | ||
| use syn::{ | ||
| parse_quote_spanned, visit_mut, Expr, ExprCall, ExprClosure, ExprForLoop, ExprIf, ExprLoop, | ||
| ExprMatch, ExprWhile, Ident, Item, | ||
| }; | ||
|
|
||
| #[derive(Debug, Default)] | ||
| pub struct BodyRewriter { | ||
| branch_lock: Arc<Mutex<()>>, | ||
| } | ||
|
|
||
| impl BodyRewriter { | ||
| fn is_branched(&self) -> bool { | ||
| self.branch_lock.try_lock().is_err() | ||
| } | ||
|
|
||
| fn with_branch<F, O>(&mut self, f: F) -> O | ||
| where | ||
| F: FnOnce(&mut BodyRewriter) -> O, | ||
| { | ||
| let branch_lock = self.branch_lock.clone(); | ||
| let _branched = branch_lock.try_lock(); | ||
| f(self) | ||
| } | ||
| } | ||
|
|
||
| impl VisitMut for BodyRewriter { | ||
| fn visit_expr_call_mut(&mut self, i: &mut ExprCall) { | ||
| let ctx_ident = Ident::new("ctx", Span::mixed_site()); | ||
|
|
||
| // Only rewrite hook calls. | ||
| if let Expr::Path(ref m) = &*i.func { | ||
| if let Some(m) = m.path.segments.last().as_ref().map(|m| &m.ident) { | ||
| if m.to_string().starts_with("use_") { | ||
| if self.is_branched() { | ||
| emit_error!(m, "hooks cannot be called at this position."); | ||
| } else { | ||
| *i = parse_quote_spanned! { i.span() => ::yew::functional::Hook::run(#i, #ctx_ident) }; | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I take it that this design means I can't call any function starting with
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is used in std as well, which I will reply to the main comment instead.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @futursolo we should mention this in the error to be more user friendly
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no way to do it. The procedural does not know the type of your expression. The only error will be from the compiler saying that |
||
| } | ||
| } | ||
|
|
||
| visit_mut::visit_expr_call_mut(self, i); | ||
| } | ||
|
|
||
| fn visit_expr_closure_mut(&mut self, i: &mut ExprClosure) { | ||
| self.with_branch(move |m| visit_mut::visit_expr_closure_mut(m, i)) | ||
| } | ||
|
|
||
| fn visit_expr_if_mut(&mut self, i: &mut ExprIf) { | ||
| for it in &mut i.attrs { | ||
| visit_mut::visit_attribute_mut(self, it); | ||
| } | ||
|
|
||
| visit_mut::visit_expr_mut(self, &mut *i.cond); | ||
|
|
||
| self.with_branch(|m| visit_mut::visit_block_mut(m, &mut i.then_branch)); | ||
|
|
||
| if let Some(it) = &mut i.else_branch { | ||
| self.with_branch(|m| visit_mut::visit_expr_mut(m, &mut *(it).1)); | ||
| } | ||
| } | ||
|
|
||
| fn visit_expr_loop_mut(&mut self, i: &mut ExprLoop) { | ||
| self.with_branch(|m| visit_mut::visit_expr_loop_mut(m, i)); | ||
| } | ||
|
|
||
| fn visit_expr_for_loop_mut(&mut self, i: &mut ExprForLoop) { | ||
| for it in &mut i.attrs { | ||
| visit_mut::visit_attribute_mut(self, it); | ||
| } | ||
| if let Some(it) = &mut i.label { | ||
| visit_mut::visit_label_mut(self, it); | ||
| } | ||
| visit_mut::visit_pat_mut(self, &mut i.pat); | ||
| visit_mut::visit_expr_mut(self, &mut *i.expr); | ||
|
|
||
| self.with_branch(|m| visit_mut::visit_block_mut(m, &mut i.body)); | ||
| } | ||
|
|
||
| fn visit_expr_match_mut(&mut self, i: &mut ExprMatch) { | ||
| for it in &mut i.attrs { | ||
| visit_mut::visit_attribute_mut(self, it); | ||
| } | ||
|
|
||
| visit_mut::visit_expr_mut(self, &mut *i.expr); | ||
|
|
||
| self.with_branch(|m| { | ||
| for it in &mut i.arms { | ||
| visit_mut::visit_arm_mut(m, it); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| fn visit_expr_while_mut(&mut self, i: &mut ExprWhile) { | ||
| for it in &mut i.attrs { | ||
| visit_mut::visit_attribute_mut(self, it); | ||
| } | ||
| if let Some(it) = &mut i.label { | ||
| visit_mut::visit_label_mut(self, it); | ||
| } | ||
|
|
||
| self.with_branch(|m| visit_mut::visit_expr_mut(m, &mut i.cond)); | ||
| self.with_branch(|m| visit_mut::visit_block_mut(m, &mut i.body)); | ||
| } | ||
|
|
||
| fn visit_item_mut(&mut self, _i: &mut Item) { | ||
| // We don't do anything for items. | ||
| // for components / hooks in other components / hooks, apply the attribute again. | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| use proc_macro2::Span; | ||
| use std::sync::{Arc, Mutex}; | ||
| use syn::visit_mut::{self, VisitMut}; | ||
| use syn::{ | ||
| GenericArgument, Lifetime, ParenthesizedGenericArguments, Receiver, TypeBareFn, TypeImplTrait, | ||
| TypeParamBound, TypeReference, | ||
| }; | ||
|
|
||
| // borrowed from the awesome async-trait crate. | ||
| pub struct CollectLifetimes { | ||
| pub elided: Vec<Lifetime>, | ||
| pub explicit: Vec<Lifetime>, | ||
| pub name: &'static str, | ||
| pub default_span: Span, | ||
|
|
||
| pub impl_trait_lock: Arc<Mutex<()>>, | ||
| pub impl_fn_lock: Arc<Mutex<()>>, | ||
| } | ||
|
|
||
| impl CollectLifetimes { | ||
| pub fn new(name: &'static str, default_span: Span) -> Self { | ||
| CollectLifetimes { | ||
| elided: Vec::new(), | ||
| explicit: Vec::new(), | ||
| name, | ||
| default_span, | ||
|
|
||
| impl_trait_lock: Arc::default(), | ||
| impl_fn_lock: Arc::default(), | ||
| } | ||
| } | ||
|
|
||
| fn is_impl_trait(&self) -> bool { | ||
| self.impl_trait_lock.try_lock().is_err() | ||
| } | ||
|
|
||
| fn is_impl_fn(&self) -> bool { | ||
| self.impl_fn_lock.try_lock().is_err() | ||
| } | ||
|
|
||
| fn visit_opt_lifetime(&mut self, lifetime: &mut Option<Lifetime>) { | ||
| match lifetime { | ||
| None => *lifetime = Some(self.next_lifetime(None)), | ||
| Some(lifetime) => self.visit_lifetime(lifetime), | ||
| } | ||
| } | ||
|
|
||
| fn visit_lifetime(&mut self, lifetime: &mut Lifetime) { | ||
| if lifetime.ident == "_" { | ||
| *lifetime = self.next_lifetime(lifetime.span()); | ||
| } else { | ||
| self.explicit.push(lifetime.clone()); | ||
| } | ||
| } | ||
|
|
||
| fn next_lifetime<S: Into<Option<Span>>>(&mut self, span: S) -> Lifetime { | ||
| let name = format!("{}{}", self.name, self.elided.len()); | ||
| let span = span.into().unwrap_or(self.default_span); | ||
| let life = Lifetime::new(&name, span); | ||
| self.elided.push(life.clone()); | ||
| life | ||
| } | ||
| } | ||
|
|
||
| impl VisitMut for CollectLifetimes { | ||
| fn visit_receiver_mut(&mut self, arg: &mut Receiver) { | ||
| if let Some((_, lifetime)) = &mut arg.reference { | ||
| self.visit_opt_lifetime(lifetime); | ||
| } | ||
| } | ||
|
|
||
| fn visit_type_reference_mut(&mut self, ty: &mut TypeReference) { | ||
| // We don't rewrite references in the impl FnOnce(&arg) or fn(&arg) | ||
| if self.is_impl_fn() { | ||
| return; | ||
| } | ||
|
|
||
| self.visit_opt_lifetime(&mut ty.lifetime); | ||
| visit_mut::visit_type_reference_mut(self, ty); | ||
| } | ||
|
|
||
| fn visit_generic_argument_mut(&mut self, gen: &mut GenericArgument) { | ||
| // We don't rewrite types in the impl FnOnce(&arg) -> Type<'_> | ||
| if self.is_impl_fn() { | ||
| return; | ||
| } | ||
|
|
||
| if let GenericArgument::Lifetime(lifetime) = gen { | ||
| self.visit_lifetime(lifetime); | ||
| } | ||
| visit_mut::visit_generic_argument_mut(self, gen); | ||
| } | ||
|
|
||
| fn visit_type_impl_trait_mut(&mut self, impl_trait: &mut TypeImplTrait) { | ||
| let impl_trait_lock = self.impl_trait_lock.clone(); | ||
| let _locked = impl_trait_lock.try_lock(); | ||
|
|
||
| impl_trait | ||
| .bounds | ||
| .insert(0, TypeParamBound::Lifetime(self.next_lifetime(None))); | ||
|
|
||
| visit_mut::visit_type_impl_trait_mut(self, impl_trait); | ||
| } | ||
|
|
||
| fn visit_parenthesized_generic_arguments_mut( | ||
| &mut self, | ||
| generic_args: &mut ParenthesizedGenericArguments, | ||
| ) { | ||
| let impl_fn_lock = self.impl_fn_lock.clone(); | ||
| let _maybe_locked = self.is_impl_trait().then(|| impl_fn_lock.try_lock()); | ||
|
|
||
| visit_mut::visit_parenthesized_generic_arguments_mut(self, generic_args); | ||
| } | ||
|
|
||
| fn visit_type_bare_fn_mut(&mut self, i: &mut TypeBareFn) { | ||
| let impl_fn_lock = self.impl_fn_lock.clone(); | ||
| let _locked = impl_fn_lock.try_lock(); | ||
|
|
||
| visit_mut::visit_type_bare_fn_mut(self, i); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.