use crate::{GenericParam, GenericParams, LifetimeName, ToTokens, TokenStream};
use quote::quote;
#[derive(Clone)]
pub enum GenericParamName {
Lifetime(LifetimeName),
Type(TokenStream),
Const(TokenStream),
}
#[derive(Clone)]
pub struct BoundedGenericParam {
pub param: GenericParamName,
pub bounds: Option<TokenStream>,
}
#[derive(Clone)]
pub struct BoundedGenericParams {
pub params: Vec<BoundedGenericParam>,
}
pub struct WithBounds<'a>(&'a BoundedGenericParams);
pub struct WithoutBounds<'a>(&'a BoundedGenericParams);
pub struct AsPhantomData<'a>(&'a BoundedGenericParams);
impl quote::ToTokens for AsPhantomData<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.0.params.len() == 1
&& let GenericParamName::Lifetime(name) = &self.0.params[0].param
{
tokens.extend(quote! { 𝟋Ph<#name> });
return;
}
let mut temp = TokenStream::new();
{
#[expect(unused)]
let tokens = ();
let mut first_param = true;
for param in &self.0.params {
if !first_param {
temp.extend(quote! { , });
}
match ¶m.param {
GenericParamName::Lifetime(name) => {
temp.extend(quote! { *mut &#name () });
}
GenericParamName::Type(name) => {
temp.extend(quote! { #name });
}
GenericParamName::Const(name) => {
temp.extend(quote! { [u32; #name] });
}
}
first_param = false;
}
if first_param {
temp.extend(quote! { () });
}
}
tokens.extend(quote! {
::core::marker::PhantomData<(#temp)>
})
}
}
impl BoundedGenericParams {
pub const fn as_phantom_data(&self) -> AsPhantomData<'_> {
AsPhantomData(self)
}
}
impl quote::ToTokens for WithBounds<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.0.params.is_empty() {
return;
}
tokens.extend(quote! {
<
});
for (i, param) in self.0.params.iter().enumerate() {
if i > 0 {
tokens.extend(quote! { , });
}
match ¶m.param {
GenericParamName::Lifetime(name) => {
tokens.extend(quote! { #name });
}
GenericParamName::Type(name) => {
tokens.extend(quote! { #name });
}
GenericParamName::Const(name) => {
tokens.extend(quote! { const #name });
}
}
if let Some(bounds) = ¶m.bounds {
tokens.extend(quote! { : #bounds });
}
}
tokens.extend(quote! {
>
});
}
}
impl quote::ToTokens for WithoutBounds<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.0.params.is_empty() {
return;
}
tokens.extend(quote! {
<
});
for (i, param) in self.0.params.iter().enumerate() {
if i > 0 {
tokens.extend(quote! { , });
}
match ¶m.param {
GenericParamName::Lifetime(name) => {
tokens.extend(quote! { #name });
}
GenericParamName::Type(name) => {
tokens.extend(quote! { #name });
}
GenericParamName::Const(name) => {
tokens.extend(quote! { #name });
}
}
}
tokens.extend(quote! {
>
});
}
}
impl BoundedGenericParams {
pub const fn display_with_bounds(&self) -> WithBounds<'_> {
WithBounds(self)
}
pub const fn display_without_bounds(&self) -> WithoutBounds<'_> {
WithoutBounds(self)
}
pub const fn display_as_phantom_data(&self) -> AsPhantomData<'_> {
AsPhantomData(self)
}
pub fn with(&self, param: BoundedGenericParam) -> Self {
let mut params = self.params.clone();
match ¶m.param {
GenericParamName::Lifetime(_) => {
let insert_position = params
.iter()
.position(|p| !matches!(p.param, GenericParamName::Lifetime(_)))
.unwrap_or(params.len());
params.insert(insert_position, param);
}
GenericParamName::Type(_) => {
let after_lifetimes = params
.iter()
.position(|p| !matches!(p.param, GenericParamName::Lifetime(_)))
.unwrap_or(params.len());
let insert_position = params[after_lifetimes..]
.iter()
.position(|p| matches!(p.param, GenericParamName::Const(_)))
.map(|pos| pos + after_lifetimes)
.unwrap_or(params.len());
params.insert(insert_position, param);
}
GenericParamName::Const(_) => {
params.push(param);
}
}
Self { params }
}
pub fn with_lifetime(&self, name: LifetimeName) -> Self {
self.with(BoundedGenericParam {
param: GenericParamName::Lifetime(name),
bounds: None,
})
}
pub fn with_type(&self, name: TokenStream) -> Self {
self.with(BoundedGenericParam {
param: GenericParamName::Type(name),
bounds: None,
})
}
}
impl BoundedGenericParams {
pub fn parse(generics: Option<&GenericParams>) -> Self {
let Some(generics) = generics else {
return Self { params: Vec::new() };
};
let mut params = Vec::new();
for param in generics.params.iter() {
match ¶m.value {
GenericParam::Type {
name,
bounds,
default: _,
} => {
params.push(BoundedGenericParam {
param: GenericParamName::Type(name.to_token_stream()),
bounds: bounds
.as_ref()
.map(|bounds| bounds.second.to_token_stream()),
});
}
GenericParam::Lifetime { name, bounds } => {
params.push(BoundedGenericParam {
param: GenericParamName::Lifetime(LifetimeName(name.name.clone())),
bounds: bounds
.as_ref()
.map(|bounds| bounds.second.to_token_stream()),
});
}
GenericParam::Const {
_const: _,
name,
_colon: _,
typ,
default: _,
} => {
params.push(BoundedGenericParam {
param: GenericParamName::Const(name.to_token_stream()),
bounds: Some(typ.to_token_stream()),
});
}
}
}
Self { params }
}
}
#[cfg(test)]
mod tests {
use super::{BoundedGenericParam, BoundedGenericParams, GenericParamName};
use crate::LifetimeName;
use quote::{ToTokens as _, quote};
fn render_to_string<T: quote::ToTokens>(t: T) -> String {
quote!(#t).to_string()
}
#[test]
fn test_empty_generic_params() {
let p = BoundedGenericParams { params: vec![] };
assert_eq!(render_to_string(p.display_with_bounds()), "");
assert_eq!(render_to_string(p.display_without_bounds()), "");
}
#[test]
fn print_multiple_generic_params() {
let p = BoundedGenericParams {
params: vec![
BoundedGenericParam {
bounds: Some(quote! { 'static }),
param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
},
BoundedGenericParam {
bounds: Some(quote! { Clone + Debug }),
param: GenericParamName::Type(quote! { T }),
},
BoundedGenericParam {
bounds: None,
param: GenericParamName::Type(quote! { U }),
},
BoundedGenericParam {
bounds: Some(quote! { usize }), param: GenericParamName::Const(quote! { N }),
},
],
};
let expected_with_bounds = quote! { <'a : 'static, T : Clone + Debug, U, const N : usize> };
assert_eq!(
p.display_with_bounds().to_token_stream().to_string(),
expected_with_bounds.to_string()
);
let expected_without_bounds = quote! { <'a, T, U, N> }; assert_eq!(
p.display_without_bounds().to_token_stream().to_string(),
expected_without_bounds.to_string()
);
}
#[test]
fn test_add_mixed_parameters() {
let mut params = BoundedGenericParams { params: vec![] };
params = params.with(BoundedGenericParam {
bounds: None,
param: GenericParamName::Type(quote! { T }),
});
params = params.with(BoundedGenericParam {
bounds: Some(quote! { usize }), param: GenericParamName::Const(quote! { N }),
});
params = params.with(BoundedGenericParam {
bounds: None,
param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
});
params = params.with(BoundedGenericParam {
bounds: Some(quote! { Clone }),
param: GenericParamName::Type(quote! { U }),
});
params = params.with(BoundedGenericParam {
bounds: Some(quote! { 'static }),
param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("b"))),
});
params = params.with(BoundedGenericParam {
bounds: Some(quote! { u8 }), param: GenericParamName::Const(quote! { M }),
});
let expected_without_bounds = quote! { <'a, 'b, T, U, N, M> };
assert_eq!(
params
.display_without_bounds()
.to_token_stream()
.to_string(),
expected_without_bounds.to_string()
);
let expected_with_bounds =
quote! { <'a, 'b : 'static, T, U : Clone, const N : usize, const M : u8> };
assert_eq!(
params.display_with_bounds().to_token_stream().to_string(),
expected_with_bounds.to_string()
);
}
#[test]
fn test_phantom_data_formatting() {
let empty = BoundedGenericParams { params: vec![] };
assert_eq!(
render_to_string(empty.display_as_phantom_data()),
":: core :: marker :: PhantomData < (()) >"
);
let lifetime = BoundedGenericParams {
params: vec![BoundedGenericParam {
param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
bounds: None,
}],
};
assert_eq!(
render_to_string(lifetime.display_as_phantom_data()),
"𝟋Ph < 'a >"
);
let type_param = BoundedGenericParams {
params: vec![BoundedGenericParam {
param: GenericParamName::Type(quote! { T }),
bounds: None,
}],
};
assert_eq!(
render_to_string(type_param.display_as_phantom_data()),
":: core :: marker :: PhantomData < (T) >"
);
let const_param = BoundedGenericParams {
params: vec![BoundedGenericParam {
param: GenericParamName::Const(quote! { N }),
bounds: None, }],
};
assert_eq!(
render_to_string(const_param.display_as_phantom_data()),
":: core :: marker :: PhantomData < ([u32 ; N]) >"
);
let mixed = BoundedGenericParams {
params: vec![
BoundedGenericParam {
param: GenericParamName::Lifetime(LifetimeName(quote::format_ident!("a"))),
bounds: None,
},
BoundedGenericParam {
param: GenericParamName::Type(quote! { T }),
bounds: Some(quote! { Clone }), },
BoundedGenericParam {
param: GenericParamName::Const(quote! { N }),
bounds: Some(quote! { usize }), },
],
};
let actual_tokens = mixed.display_as_phantom_data();
let expected_tokens = quote! {
::core::marker::PhantomData<(*mut &'a (), T, [u32; N])>
};
assert_eq!(
actual_tokens.to_token_stream().to_string(),
expected_tokens.to_string()
);
}
}