Skip to content

Commit 06e736b

Browse files
committed
Merge remote-tracking branch 'upstream/master' into dom-bundle
2 parents eca11f4 + 485a1b8 commit 06e736b

40 files changed

Lines changed: 1750 additions & 917 deletions

.github/workflows/benchmark.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
- uses: actions/checkout@v2
5454
with:
5555
path: "./yew"
56+
repository: ${{ github.event.pull_request.head.repo.full_name }}
5657
ref: "${{ github.head_ref }}"
5758

5859
- uses: actions/checkout@v2

examples/function_todomvc/src/hooks/use_bool_toggle.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ops::Deref;
22
use std::rc::Rc;
3-
use yew::{use_state_eq, UseStateHandle};
3+
use yew::prelude::*;
44

55
#[derive(Clone)]
66
pub struct UseBoolToggleHandle {
@@ -47,6 +47,7 @@ impl Deref for UseBoolToggleHandle {
4747
/// <button {onclick}>{ "Click me" }</button>
4848
/// ...
4949
/// ```
50+
#[hook]
5051
pub fn use_bool_toggle(default: bool) -> UseBoolToggleHandle {
5152
let state = use_state_eq(|| default);
5253

examples/simple_ssr/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ impl PartialEq for UuidState {
5757
}
5858
}
5959

60+
#[hook]
6061
fn use_random_uuid() -> SuspensionResult<Uuid> {
6162
let s = use_state(UuidState::new);
6263

examples/suspense/src/use_sleep.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ impl Reducible for SleepState {
2828
}
2929
}
3030

31+
#[hook]
3132
pub fn use_sleep() -> SuspensionResult<Rc<dyn Fn()>> {
3233
let sleep_state = use_reducer(SleepState::new);
3334

packages/yew-agent/src/hooks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ where
2929
///
3030
/// Takes a callback as the only argument. The callback will be updated on every render to make
3131
/// sure captured values (if any) are up to date.
32+
#[hook]
3233
pub fn use_bridge<T, F>(on_output: F) -> UseBridgeHandle<T>
3334
where
3435
T: Bridged,

packages/yew-macro/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ lazy_static = "1"
2121
proc-macro-error = "1"
2222
proc-macro2 = "1"
2323
quote = "1"
24-
syn = { version = "1", features = ["full", "extra-traits"] }
24+
syn = { version = "1", features = ["full", "extra-traits", "visit-mut"] }
25+
once_cell = "1"
26+
prettyplease = "0.1.1"
2527

2628
# testing
2729
[dev-dependencies]

packages/yew-macro/src/function_component.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ use quote::{format_ident, quote, ToTokens};
33
use syn::parse::{Parse, ParseStream};
44
use syn::punctuated::Punctuated;
55
use syn::token::{Comma, Fn};
6-
use syn::{Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility};
6+
use syn::{
7+
visit_mut, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility,
8+
};
9+
10+
use crate::hook::BodyRewriter;
711

812
#[derive(Clone)]
913
pub struct FunctionComponent {
@@ -169,7 +173,7 @@ fn print_fn(func_comp: FunctionComponent, use_fn_name: bool) -> TokenStream {
169173
fn_token,
170174
name,
171175
attrs,
172-
block,
176+
mut block,
173177
return_type,
174178
generics,
175179
arg,
@@ -184,9 +188,14 @@ fn print_fn(func_comp: FunctionComponent, use_fn_name: bool) -> TokenStream {
184188
Ident::new("inner", Span::mixed_site())
185189
};
186190

191+
let ctx_ident = Ident::new("ctx", Span::mixed_site());
192+
193+
let mut body_rewriter = BodyRewriter::default();
194+
visit_mut::visit_block_mut(&mut body_rewriter, &mut *block);
195+
187196
quote! {
188197
#(#attrs)*
189-
#fn_token #name #ty_generics (#arg) -> #return_type
198+
#fn_token #name #ty_generics (#ctx_ident: &mut ::yew::functional::HookContext, #arg) -> #return_type
190199
#where_clause
191200
{
192201
#block
@@ -241,6 +250,8 @@ pub fn function_component_impl(
241250
Ident::new("inner", Span::mixed_site())
242251
};
243252

253+
let ctx_ident = Ident::new("ctx", Span::mixed_site());
254+
244255
let quoted = quote! {
245256
#[doc(hidden)]
246257
#[allow(non_camel_case_types)]
@@ -253,10 +264,10 @@ pub fn function_component_impl(
253264
impl #impl_generics ::yew::functional::FunctionProvider for #provider_name #ty_generics #where_clause {
254265
type TProps = #props_type;
255266

256-
fn run(#provider_props: &Self::TProps) -> ::yew::html::HtmlResult {
267+
fn run(#ctx_ident: &mut ::yew::functional::HookContext, #provider_props: &Self::TProps) -> ::yew::html::HtmlResult {
257268
#func
258269

259-
::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#provider_props))
270+
::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#ctx_ident, #provider_props))
260271
}
261272
}
262273

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use proc_macro2::Span;
2+
use proc_macro_error::emit_error;
3+
use std::sync::{Arc, Mutex};
4+
use syn::spanned::Spanned;
5+
use syn::visit_mut::VisitMut;
6+
use syn::{
7+
parse_quote_spanned, visit_mut, Expr, ExprCall, ExprClosure, ExprForLoop, ExprIf, ExprLoop,
8+
ExprMatch, ExprWhile, Ident, Item,
9+
};
10+
11+
#[derive(Debug, Default)]
12+
pub struct BodyRewriter {
13+
branch_lock: Arc<Mutex<()>>,
14+
}
15+
16+
impl BodyRewriter {
17+
fn is_branched(&self) -> bool {
18+
self.branch_lock.try_lock().is_err()
19+
}
20+
21+
fn with_branch<F, O>(&mut self, f: F) -> O
22+
where
23+
F: FnOnce(&mut BodyRewriter) -> O,
24+
{
25+
let branch_lock = self.branch_lock.clone();
26+
let _branched = branch_lock.try_lock();
27+
f(self)
28+
}
29+
}
30+
31+
impl VisitMut for BodyRewriter {
32+
fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
33+
let ctx_ident = Ident::new("ctx", Span::mixed_site());
34+
35+
// Only rewrite hook calls.
36+
if let Expr::Path(ref m) = &*i.func {
37+
if let Some(m) = m.path.segments.last().as_ref().map(|m| &m.ident) {
38+
if m.to_string().starts_with("use_") {
39+
if self.is_branched() {
40+
emit_error!(
41+
m,
42+
"hooks cannot be called at this position.";
43+
help = "move hooks to the top-level of your function.";
44+
note = "see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks"
45+
);
46+
} else {
47+
*i = parse_quote_spanned! { i.span() => ::yew::functional::Hook::run(#i, #ctx_ident) };
48+
}
49+
50+
return;
51+
}
52+
}
53+
}
54+
55+
visit_mut::visit_expr_call_mut(self, i);
56+
}
57+
58+
fn visit_expr_closure_mut(&mut self, i: &mut ExprClosure) {
59+
self.with_branch(move |m| visit_mut::visit_expr_closure_mut(m, i))
60+
}
61+
62+
fn visit_expr_if_mut(&mut self, i: &mut ExprIf) {
63+
for it in &mut i.attrs {
64+
visit_mut::visit_attribute_mut(self, it);
65+
}
66+
67+
visit_mut::visit_expr_mut(self, &mut *i.cond);
68+
69+
self.with_branch(|m| visit_mut::visit_block_mut(m, &mut i.then_branch));
70+
71+
if let Some(it) = &mut i.else_branch {
72+
self.with_branch(|m| visit_mut::visit_expr_mut(m, &mut *(it).1));
73+
}
74+
}
75+
76+
fn visit_expr_loop_mut(&mut self, i: &mut ExprLoop) {
77+
self.with_branch(|m| visit_mut::visit_expr_loop_mut(m, i));
78+
}
79+
80+
fn visit_expr_for_loop_mut(&mut self, i: &mut ExprForLoop) {
81+
for it in &mut i.attrs {
82+
visit_mut::visit_attribute_mut(self, it);
83+
}
84+
if let Some(it) = &mut i.label {
85+
visit_mut::visit_label_mut(self, it);
86+
}
87+
visit_mut::visit_pat_mut(self, &mut i.pat);
88+
visit_mut::visit_expr_mut(self, &mut *i.expr);
89+
90+
self.with_branch(|m| visit_mut::visit_block_mut(m, &mut i.body));
91+
}
92+
93+
fn visit_expr_match_mut(&mut self, i: &mut ExprMatch) {
94+
for it in &mut i.attrs {
95+
visit_mut::visit_attribute_mut(self, it);
96+
}
97+
98+
visit_mut::visit_expr_mut(self, &mut *i.expr);
99+
100+
self.with_branch(|m| {
101+
for it in &mut i.arms {
102+
visit_mut::visit_arm_mut(m, it);
103+
}
104+
});
105+
}
106+
107+
fn visit_expr_while_mut(&mut self, i: &mut ExprWhile) {
108+
for it in &mut i.attrs {
109+
visit_mut::visit_attribute_mut(self, it);
110+
}
111+
if let Some(it) = &mut i.label {
112+
visit_mut::visit_label_mut(self, it);
113+
}
114+
115+
self.with_branch(|m| visit_mut::visit_expr_mut(m, &mut i.cond));
116+
self.with_branch(|m| visit_mut::visit_block_mut(m, &mut i.body));
117+
}
118+
119+
fn visit_item_mut(&mut self, _i: &mut Item) {
120+
// We don't do anything for items.
121+
// for components / hooks in other components / hooks, apply the attribute again.
122+
}
123+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use proc_macro2::Span;
2+
use std::sync::{Arc, Mutex};
3+
use syn::visit_mut::{self, VisitMut};
4+
use syn::{
5+
GenericArgument, Lifetime, ParenthesizedGenericArguments, Receiver, TypeBareFn, TypeImplTrait,
6+
TypeParamBound, TypeReference,
7+
};
8+
9+
// borrowed from the awesome async-trait crate.
10+
pub struct CollectLifetimes {
11+
pub elided: Vec<Lifetime>,
12+
pub explicit: Vec<Lifetime>,
13+
pub name: &'static str,
14+
pub default_span: Span,
15+
16+
pub impl_trait_lock: Arc<Mutex<()>>,
17+
pub impl_fn_lock: Arc<Mutex<()>>,
18+
}
19+
20+
impl CollectLifetimes {
21+
pub fn new(name: &'static str, default_span: Span) -> Self {
22+
CollectLifetimes {
23+
elided: Vec::new(),
24+
explicit: Vec::new(),
25+
name,
26+
default_span,
27+
28+
impl_trait_lock: Arc::default(),
29+
impl_fn_lock: Arc::default(),
30+
}
31+
}
32+
33+
fn is_impl_trait(&self) -> bool {
34+
self.impl_trait_lock.try_lock().is_err()
35+
}
36+
37+
fn is_impl_fn(&self) -> bool {
38+
self.impl_fn_lock.try_lock().is_err()
39+
}
40+
41+
fn visit_opt_lifetime(&mut self, lifetime: &mut Option<Lifetime>) {
42+
match lifetime {
43+
None => *lifetime = Some(self.next_lifetime(None)),
44+
Some(lifetime) => self.visit_lifetime(lifetime),
45+
}
46+
}
47+
48+
fn visit_lifetime(&mut self, lifetime: &mut Lifetime) {
49+
if lifetime.ident == "_" {
50+
*lifetime = self.next_lifetime(lifetime.span());
51+
} else {
52+
self.explicit.push(lifetime.clone());
53+
}
54+
}
55+
56+
fn next_lifetime<S: Into<Option<Span>>>(&mut self, span: S) -> Lifetime {
57+
let name = format!("{}{}", self.name, self.elided.len());
58+
let span = span.into().unwrap_or(self.default_span);
59+
let life = Lifetime::new(&name, span);
60+
self.elided.push(life.clone());
61+
life
62+
}
63+
}
64+
65+
impl VisitMut for CollectLifetimes {
66+
fn visit_receiver_mut(&mut self, arg: &mut Receiver) {
67+
if let Some((_, lifetime)) = &mut arg.reference {
68+
self.visit_opt_lifetime(lifetime);
69+
}
70+
}
71+
72+
fn visit_type_reference_mut(&mut self, ty: &mut TypeReference) {
73+
// We don't rewrite references in the impl FnOnce(&arg) or fn(&arg)
74+
if self.is_impl_fn() {
75+
return;
76+
}
77+
78+
self.visit_opt_lifetime(&mut ty.lifetime);
79+
visit_mut::visit_type_reference_mut(self, ty);
80+
}
81+
82+
fn visit_generic_argument_mut(&mut self, gen: &mut GenericArgument) {
83+
// We don't rewrite types in the impl FnOnce(&arg) -> Type<'_>
84+
if self.is_impl_fn() {
85+
return;
86+
}
87+
88+
if let GenericArgument::Lifetime(lifetime) = gen {
89+
self.visit_lifetime(lifetime);
90+
}
91+
visit_mut::visit_generic_argument_mut(self, gen);
92+
}
93+
94+
fn visit_type_impl_trait_mut(&mut self, impl_trait: &mut TypeImplTrait) {
95+
let impl_trait_lock = self.impl_trait_lock.clone();
96+
let _locked = impl_trait_lock.try_lock();
97+
98+
impl_trait
99+
.bounds
100+
.insert(0, TypeParamBound::Lifetime(self.next_lifetime(None)));
101+
102+
visit_mut::visit_type_impl_trait_mut(self, impl_trait);
103+
}
104+
105+
fn visit_parenthesized_generic_arguments_mut(
106+
&mut self,
107+
generic_args: &mut ParenthesizedGenericArguments,
108+
) {
109+
let impl_fn_lock = self.impl_fn_lock.clone();
110+
let _maybe_locked = self.is_impl_trait().then(|| impl_fn_lock.try_lock());
111+
112+
visit_mut::visit_parenthesized_generic_arguments_mut(self, generic_args);
113+
}
114+
115+
fn visit_type_bare_fn_mut(&mut self, i: &mut TypeBareFn) {
116+
let impl_fn_lock = self.impl_fn_lock.clone();
117+
let _locked = impl_fn_lock.try_lock();
118+
119+
visit_mut::visit_type_bare_fn_mut(self, i);
120+
}
121+
}

0 commit comments

Comments
 (0)