Skip to content

Commit

Permalink
#[hook]: clippy::multiple_bound_locations lint no longer triggered
Browse files Browse the repository at this point in the history
This is achieved by reworking the logic for rewriting the function signature of the hook.
  • Loading branch information
its-the-shrimp committed Feb 26, 2025
1 parent 0091679 commit c2f164f
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 60 deletions.
4 changes: 0 additions & 4 deletions packages/yew-macro/src/hook/lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use syn::{
// 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,

Expand All @@ -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,

Expand Down Expand Up @@ -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());
}
}

Expand Down
51 changes: 37 additions & 14 deletions packages/yew-macro/src/hook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 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 span = self.inner.span();

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!(
Expand All @@ -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(),
}),
}),
}
}
}

Expand Down
120 changes: 80 additions & 40 deletions packages/yew-macro/src/hook/signature.rs
Original file line number Diff line number Diff line change
@@ -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(&param.ident),
_ => false,
}
}

#[derive(Default)]
pub struct CollectArgs {
needs_boxing: bool,
Expand Down Expand Up @@ -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;

parse_quote!(#hook_lifetime, #(#elided_lifetimes,)* #params)
};

let mut where_clause = generics
.where_clause
.clone()
.unwrap_or_else(|| WhereClause {
where_token: token::Where {
span: Span::mixed_site(),
},
predicates: Default::default(),
});
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();

for elided in lifetimes.elided.iter() {
where_clause
.predicates
.push(parse_quote!(#elided: #hook_lifetime));
}
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());
}
}

for explicit in lifetimes.explicit.iter() {
where_clause
.predicates
.push(parse_quote!(#explicit: #hook_lifetime));
}
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 type_param in generics.type_params() {
let type_param_ident = &type_param.ident;
where_clause
.predicates
.push(parse_quote!(#type_param_ident: #hook_lifetime));
GenericParam::Const(_) => {}
}
}

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;
Expand All @@ -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()
}

Expand Down
4 changes: 2 additions & 2 deletions packages/yew-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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() {
Expand Down

0 comments on commit c2f164f

Please sign in to comment.