diff --git a/packages/yew-macro/src/hook/lifetime.rs b/packages/yew-macro/src/hook/lifetime.rs index 03818effa..3c2dc07bb 100644 --- a/packages/yew-macro/src/hook/lifetime.rs +++ b/packages/yew-macro/src/hook/lifetime.rs @@ -10,7 +10,6 @@ use syn::{ // borrowed from the awesome async-trait crate. pub struct CollectLifetimes { pub elided: Vec, - pub explicit: Vec, pub name: &'static str, pub default_span: Span, @@ -23,7 +22,6 @@ impl CollectLifetimes { pub fn new(name: &'static str, default_span: Span) -> Self { CollectLifetimes { elided: Vec::new(), - explicit: Vec::new(), name, default_span, @@ -55,8 +53,6 @@ impl CollectLifetimes { fn visit_lifetime(&mut self, lifetime: &mut Lifetime) { if lifetime.ident == "_" { *lifetime = self.next_lifetime(lifetime.span()); - } else { - self.explicit.push(lifetime.clone()); } } diff --git a/packages/yew-macro/src/hook/mod.rs b/packages/yew-macro/src/hook/mod.rs index a60ecbd55..df83c3575 100644 --- a/packages/yew-macro/src/hook/mod.rs +++ b/packages/yew-macro/src/hook/mod.rs @@ -2,9 +2,10 @@ use proc_macro2::{Span, TokenStream}; use proc_macro_error::emit_error; use quote::quote; use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; use syn::{ - parse_file, parse_quote, visit_mut, Attribute, Ident, ItemFn, LitStr, ReturnType, Signature, - Type, + visit_mut, AttrStyle, Attribute, Block, Expr, ExprPath, File, Ident, Item, ItemFn, LitStr, + Meta, MetaNameValue, ReturnType, Signature, Stmt, Token, Type, }; mod body; @@ -51,16 +52,26 @@ impl Parse for HookFn { impl HookFn { fn doc_attr(&self) -> Attribute { - let vis = &self.inner.vis; - let sig = &self.inner.sig; + let span = self.inner.span(); - let sig_s = quote! { #vis #sig { - __yew_macro_dummy_function_body__ - } } - .to_string(); - - let sig_file = parse_file(&sig_s).unwrap(); - let sig_formatted = prettyplease::unparse(&sig_file); + let sig_formatted = prettyplease::unparse(&File { + shebang: None, + attrs: vec![], + items: vec![Item::Fn(ItemFn { + block: Box::new(Block { + brace_token: Default::default(), + stmts: vec![Stmt::Expr( + Expr::Path(ExprPath { + attrs: vec![], + qself: None, + path: Ident::new("__yew_macro_dummy_function_body__", span).into(), + }), + None, + )], + }), + ..self.inner.clone() + })], + }); let literal = LitStr::new( &format!( @@ -78,10 +89,22 @@ When used in function components and hooks, this hook is equivalent to: "/* implementation omitted */" ) ), - Span::mixed_site(), + span, ); - parse_quote!(#[doc = #literal]) + Attribute { + pound_token: Default::default(), + style: AttrStyle::Outer, + bracket_token: Default::default(), + meta: Meta::NameValue(MetaNameValue { + path: Ident::new("doc", span).into(), + eq_token: Token![=](span), + value: Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: literal.into(), + }), + }), + } } } diff --git a/packages/yew-macro/src/hook/signature.rs b/packages/yew-macro/src/hook/signature.rs index f4772619d..12c68c5f3 100644 --- a/packages/yew-macro/src/hook/signature.rs +++ b/packages/yew-macro/src/hook/signature.rs @@ -1,15 +1,27 @@ +use std::iter::once; +use std::mem::take; + use proc_macro2::{Span, TokenStream}; use proc_macro_error::emit_error; use quote::{quote, ToTokens}; +use syn::punctuated::{Pair, Punctuated}; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; use syn::{ - parse_quote, parse_quote_spanned, token, visit_mut, FnArg, GenericParam, Ident, Lifetime, Pat, - Receiver, ReturnType, Signature, Type, TypeImplTrait, TypeReference, WhereClause, + parse_quote, parse_quote_spanned, visit_mut, FnArg, GenericParam, Ident, Lifetime, + LifetimeParam, Pat, Receiver, ReturnType, Signature, Type, TypeImplTrait, TypeParam, + TypeParamBound, TypeReference, WherePredicate, }; use super::lifetime; +fn type_is_generic(ty: &Type, param: &TypeParam) -> bool { + match ty { + Type::Path(path) => path.path.is_ident(¶m.ident), + _ => false, + } +} + #[derive(Default)] pub struct CollectArgs { needs_boxing: bool, @@ -99,48 +111,68 @@ impl HookSignature { .. } = sig; - let hook_lifetime = { - let hook_lifetime = Lifetime::new("'hook", Span::mixed_site()); - generics.params = { - let elided_lifetimes = &lifetimes.elided; - let params = &generics.params; + let hook_lifetime = Lifetime::new("'hook", Span::mixed_site()); + let mut params: Punctuated<_, _> = once(hook_lifetime.clone()) + .chain(lifetimes.elided) + .map(|lifetime| { + GenericParam::Lifetime(LifetimeParam { + attrs: vec![], + lifetime, + colon_token: None, + bounds: Default::default(), + }) + }) + .map(|param| Pair::new(param, Some(Default::default()))) + .chain(take(&mut generics.params).into_pairs()) + .collect(); - parse_quote!(#hook_lifetime, #(#elided_lifetimes,)* #params) - }; + for type_param in params.iter_mut().skip(1) { + match type_param { + GenericParam::Lifetime(param) => { + if let Some(predicate) = generics + .where_clause + .iter_mut() + .flat_map(|c| &mut c.predicates) + .find_map(|predicate| match predicate { + WherePredicate::Lifetime(p) if p.lifetime == param.lifetime => Some(p), + _ => None, + }) + { + predicate.bounds.push(hook_lifetime.clone()); + } else { + param.colon_token = Some(param.colon_token.unwrap_or_default()); + param.bounds.push(hook_lifetime.clone()); + } + } - let mut where_clause = generics - .where_clause - .clone() - .unwrap_or_else(|| WhereClause { - where_token: token::Where { - span: Span::mixed_site(), - }, - predicates: Default::default(), - }); + GenericParam::Type(param) => { + if let Some(predicate) = generics + .where_clause + .iter_mut() + .flat_map(|c| &mut c.predicates) + .find_map(|predicate| match predicate { + WherePredicate::Type(p) if type_is_generic(&p.bounded_ty, param) => { + Some(p) + } + _ => None, + }) + { + predicate + .bounds + .push(TypeParamBound::Lifetime(hook_lifetime.clone())); + } else { + param.colon_token = Some(param.colon_token.unwrap_or_default()); + param + .bounds + .push(TypeParamBound::Lifetime(hook_lifetime.clone())); + } + } - for elided in lifetimes.elided.iter() { - where_clause - .predicates - .push(parse_quote!(#elided: #hook_lifetime)); + GenericParam::Const(_) => {} } + } - for explicit in lifetimes.explicit.iter() { - where_clause - .predicates - .push(parse_quote!(#explicit: #hook_lifetime)); - } - - for type_param in generics.type_params() { - let type_param_ident = &type_param.ident; - where_clause - .predicates - .push(parse_quote!(#type_param_ident: #hook_lifetime)); - } - - generics.where_clause = Some(where_clause); - - hook_lifetime - }; + generics.params = params; let (output, output_type) = Self::rewrite_return_type(&hook_lifetime, return_type); sig.output = output; @@ -165,7 +197,15 @@ impl HookSignature { self.sig .generics .lifetimes() - .map(|life| parse_quote! { &#life () }) + .map(|life| TypeReference { + and_token: Default::default(), + lifetime: Some(life.lifetime.clone()), + mutability: None, + elem: Box::new(Type::Tuple(syn::TypeTuple { + paren_token: Default::default(), + elems: Default::default(), + })), + }) .collect() } diff --git a/packages/yew-macro/src/lib.rs b/packages/yew-macro/src/lib.rs index acc0d4f8d..cac2ac423 100644 --- a/packages/yew-macro/src/lib.rs +++ b/packages/yew-macro/src/lib.rs @@ -140,7 +140,7 @@ pub fn classes(input: TokenStream) -> TokenStream { #[proc_macro_error::proc_macro_error] #[proc_macro_attribute] -pub fn function_component(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream { +pub fn function_component(attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as FunctionComponent); let attr = parse_macro_input!(attr as FunctionComponentName); @@ -151,7 +151,7 @@ pub fn function_component(attr: TokenStream, item: TokenStream) -> proc_macro::T #[proc_macro_error::proc_macro_error] #[proc_macro_attribute] -pub fn hook(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream { +pub fn hook(attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as HookFn); if let Some(m) = proc_macro2::TokenStream::from(attr).into_iter().next() {