From 25e82b177c8bad26509e1e989795f63ef08e9dc5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:33 +0200 Subject: [PATCH] rust: pin-init: internal: add utility to handle generics merging There're many cases where we want to merge multiple generics together. Currently this is done by cloning generics and modifying it, making the code less readable. Add a utility code that allow printing multiple generics together, ensuring correct ordering between lifetime and other parameters. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-3-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 10 +- rust/pin-init/internal/src/util.rs | 164 ++++++++++++++++++++++++- 2 files changed, 168 insertions(+), 6 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 982a38ceea93..4ffa35bbbccd 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -321,10 +321,12 @@ fn generate_projections( is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { - let (impl_generics, ty_generics, _) = generics.split_for_impl(); - let mut generics_with_pin_lt = generics.clone(); - generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); - let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); + let pin_lt_generics: Generics = parse_quote!(<'__pin>); + let generics_with_pin_lt = CombinedGenerics(vec![&pin_lt_generics, generics]); + + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl(); + let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index 0b5c28b21091..2a57a25b70ae 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::{Ident, TokenStream}; -use quote::format_ident; -use syn::{Attribute, Index, Member}; +use quote::{format_ident, ToTokens}; +use syn::{Attribute, GenericParam, Generics, Index, Member, Token}; pub(crate) trait AttrListExt { fn extract_cfg_attrs(&mut self) -> Vec; @@ -52,3 +52,163 @@ fn display_name(&self) -> String { } } } + +pub(crate) struct CombinedGenerics<'a>(pub(crate) Vec<&'a Generics>); +pub(crate) struct CombinedImplGenerics<'a>(&'a CombinedGenerics<'a>); +pub(crate) struct CombinedTypeGenerics<'a>(&'a CombinedGenerics<'a>); + +impl CombinedGenerics<'_> { + pub(crate) fn split_for_impl( + &self, + ) -> ( + CombinedImplGenerics<'_>, + CombinedTypeGenerics<'_>, + // A stub type so `split_for_impl` signature matches that of `syn`'s. + impl Sized, + ) { + (CombinedImplGenerics(self), CombinedTypeGenerics(self), ()) + } +} + +impl ToTokens for CombinedGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + // Most of the time we are only adding lifetimes, so we prefer to place + // the user provided generics last. + self.0 + .last() + .and_then(|x| x.lt_token) + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + // Output lifetimes first. + for generics in self.0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(lt) = param.value() { + lt.to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + } + + for generics in self.0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(_) = param.value() { + continue; + }; + param.value().to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + + self.0 + .last() + .and_then(|x| x.gt_token) + .unwrap_or_default() + .to_tokens(tokens); + } +} + +impl ToTokens for CombinedImplGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0 + .0 + .last() + .and_then(|x| x.lt_token) + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + // Output lifetimes first. + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(lt) = param.value() { + lt.to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + } + + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + // Leave out defaults. + match param.value() { + GenericParam::Lifetime(_) => continue, + GenericParam::Type(param) => { + param.ident.to_tokens(tokens); + if !param.bounds.is_empty() { + param + .colon_token + .unwrap_or_else(Default::default) + .to_tokens(tokens); + param.bounds.to_tokens(tokens); + } + } + GenericParam::Const(param) => { + param.const_token.to_tokens(tokens); + param.ident.to_tokens(tokens); + param.colon_token.to_tokens(tokens); + param.ty.to_tokens(tokens); + } + } + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + + self.0 + .0 + .last() + .and_then(|x| x.gt_token) + .unwrap_or_default() + .to_tokens(tokens); + } +} + +impl ToTokens for CombinedTypeGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0 + .0 + .last() + .and_then(|x| x.lt_token) + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + // Output lifetimes first. + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(lt) = param.value() { + // Leave out bounds + lt.lifetime.to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + } + + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + // Leave out bounds and defaults. + match param.value() { + GenericParam::Lifetime(_) => continue, + GenericParam::Type(param) => { + param.ident.to_tokens(tokens); + } + GenericParam::Const(param) => { + param.ident.to_tokens(tokens); + } + } + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + + self.0 + .0 + .last() + .and_then(|x| x.gt_token) + .unwrap_or_default() + .to_tokens(tokens); + } +}