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
#3803)

This is achieved by reworking the logic for rewriting the function signature of the hook.
  • Loading branch information
its-the-shrimp authored Feb 26, 2025
1 parent 3bccc1e commit 48cdc3d
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 48cdc3d

Please sign in to comment.