#[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.
pub struct CollectLifetimes {
pub elided: Vec<Lifetime>,
pub explicit: Vec<Lifetime>,
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());
}
}

View File

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

View File

@ -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,
@ -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 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)
};
let mut where_clause = generics
for type_param in params.iter_mut().skip(1) {
match type_param {
GenericParam::Lifetime(param) => {
if let Some(predicate) = generics
.where_clause
.clone()
.unwrap_or_else(|| WhereClause {
where_token: token::Where {
span: Span::mixed_site(),
},
predicates: Default::default(),
});
for elided in lifetimes.elided.iter() {
where_clause
.predicates
.push(parse_quote!(#elided: #hook_lifetime));
.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;
@ -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()
}

View File

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