#[hook]: clippy::multiple_bound_locations lint no longer triggered (#3803)

This is achieved by reworking the logic for rewriting the function signature of the hook.
This commit is contained in:
Tim Kurdov 2025-02-26 13:57:18 +00:00 committed by GitHub
parent 3bccc1e7e6
commit 48cdc3dff4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 118 additions and 59 deletions

View File

@ -10,7 +10,6 @@ use syn::{
// borrowed from the awesome async-trait crate. // borrowed from the awesome async-trait crate.
pub struct CollectLifetimes { pub struct CollectLifetimes {
pub elided: Vec<Lifetime>, pub elided: Vec<Lifetime>,
pub explicit: Vec<Lifetime>,
pub name: &'static str, pub name: &'static str,
pub default_span: Span, pub default_span: Span,
@ -23,7 +22,6 @@ impl CollectLifetimes {
pub fn new(name: &'static str, default_span: Span) -> Self { pub fn new(name: &'static str, default_span: Span) -> Self {
CollectLifetimes { CollectLifetimes {
elided: Vec::new(), elided: Vec::new(),
explicit: Vec::new(),
name, name,
default_span, default_span,
@ -55,8 +53,6 @@ impl CollectLifetimes {
fn visit_lifetime(&mut self, lifetime: &mut Lifetime) { fn visit_lifetime(&mut self, lifetime: &mut Lifetime) {
if lifetime.ident == "_" { if lifetime.ident == "_" {
*lifetime = self.next_lifetime(lifetime.span()); *lifetime = self.next_lifetime(lifetime.span());
} else {
self.explicit.push(lifetime.clone());
} }
} }

View File

@ -2,9 +2,10 @@ use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error; use proc_macro_error::emit_error;
use quote::quote; use quote::quote;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{ use syn::{
parse_file, parse_quote, visit_mut, Attribute, Ident, ItemFn, LitStr, ReturnType, Signature, visit_mut, AttrStyle, Attribute, Block, Expr, ExprPath, File, Ident, Item, ItemFn, LitStr,
Type, Meta, MetaNameValue, ReturnType, Signature, Stmt, Token, Type,
}; };
mod body; mod body;
@ -51,16 +52,26 @@ impl Parse for HookFn {
impl HookFn { impl HookFn {
fn doc_attr(&self) -> Attribute { fn doc_attr(&self) -> Attribute {
let vis = &self.inner.vis; let span = self.inner.span();
let sig = &self.inner.sig;
let sig_s = quote! { #vis #sig { let sig_formatted = prettyplease::unparse(&File {
__yew_macro_dummy_function_body__ shebang: None,
} } attrs: vec![],
.to_string(); items: vec![Item::Fn(ItemFn {
block: Box::new(Block {
let sig_file = parse_file(&sig_s).unwrap(); brace_token: Default::default(),
let sig_formatted = prettyplease::unparse(&sig_file); 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( let literal = LitStr::new(
&format!( &format!(
@ -78,10 +89,22 @@ When used in function components and hooks, this hook is equivalent to:
"/* implementation omitted */" "/* 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(),
}),
}),
}
} }
} }

View File

@ -1,15 +1,27 @@
use std::iter::once;
use std::mem::take;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error; use proc_macro_error::emit_error;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::punctuated::{Pair, Punctuated};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::visit_mut::VisitMut; use syn::visit_mut::VisitMut;
use syn::{ use syn::{
parse_quote, parse_quote_spanned, token, visit_mut, FnArg, GenericParam, Ident, Lifetime, Pat, parse_quote, parse_quote_spanned, visit_mut, FnArg, GenericParam, Ident, Lifetime,
Receiver, ReturnType, Signature, Type, TypeImplTrait, TypeReference, WhereClause, LifetimeParam, Pat, Receiver, ReturnType, Signature, Type, TypeImplTrait, TypeParam,
TypeParamBound, TypeReference, WherePredicate,
}; };
use super::lifetime; 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)] #[derive(Default)]
pub struct CollectArgs { pub struct CollectArgs {
needs_boxing: bool, needs_boxing: bool,
@ -99,48 +111,68 @@ impl HookSignature {
.. ..
} = sig; } = sig;
let hook_lifetime = { let hook_lifetime = Lifetime::new("'hook", Span::mixed_site());
let hook_lifetime = Lifetime::new("'hook", Span::mixed_site()); let mut params: Punctuated<_, _> = once(hook_lifetime.clone())
generics.params = { .chain(lifetimes.elided)
let elided_lifetimes = &lifetimes.elided; .map(|lifetime| {
let params = &generics.params; 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 GenericParam::Type(param) => {
.where_clause if let Some(predicate) = generics
.clone() .where_clause
.unwrap_or_else(|| WhereClause { .iter_mut()
where_token: token::Where { .flat_map(|c| &mut c.predicates)
span: Span::mixed_site(), .find_map(|predicate| match predicate {
}, WherePredicate::Type(p) if type_is_generic(&p.bounded_ty, param) => {
predicates: Default::default(), 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() { GenericParam::Const(_) => {}
where_clause
.predicates
.push(parse_quote!(#elided: #hook_lifetime));
} }
}
for explicit in lifetimes.explicit.iter() { generics.params = params;
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
};
let (output, output_type) = Self::rewrite_return_type(&hook_lifetime, return_type); let (output, output_type) = Self::rewrite_return_type(&hook_lifetime, return_type);
sig.output = output; sig.output = output;
@ -165,7 +197,15 @@ impl HookSignature {
self.sig self.sig
.generics .generics
.lifetimes() .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() .collect()
} }

View File

@ -140,7 +140,7 @@ pub fn classes(input: TokenStream) -> TokenStream {
#[proc_macro_error::proc_macro_error] #[proc_macro_error::proc_macro_error]
#[proc_macro_attribute] #[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 item = parse_macro_input!(item as FunctionComponent);
let attr = parse_macro_input!(attr as FunctionComponentName); 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_error::proc_macro_error]
#[proc_macro_attribute] #[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); let item = parse_macro_input!(item as HookFn);
if let Some(m) = proc_macro2::TokenStream::from(attr).into_iter().next() { if let Some(m) = proc_macro2::TokenStream::from(attr).into_iter().next() {