use crate::fq_std::{FQClone, FQSend, FQSync};
use proc_macro::{TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use std::collections::HashSet;
use syn::{spanned::Spanned, Ident};
pub fn ensure_no_collision(value: Ident, haystack: TokenStream) -> Ident {
let idents = {
let mut unvisited = vec![haystack];
let mut found = HashSet::new();
while let Some(tokens) = unvisited.pop() {
for t in tokens {
match t {
TokenTree::Ident(ident) => {
found.insert(ident.to_string());
}
TokenTree::Group(g) => unvisited.push(g.stream()),
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
}
}
}
found
};
let span = value.span();
let mut value = value.to_string();
while idents.contains(&value) {
value.push('X');
}
Ident::new(&value, span)
}
pub fn derive_label(
input: syn::DeriveInput,
trait_name: &str,
trait_path: &syn::Path,
) -> TokenStream {
if let syn::Data::Union(_) = &input.data {
let message = format!("Cannot derive {trait_name} for unions.");
return quote_spanned! {
input.span() => ::core::compile_error!(#message);
}
.into();
}
let ident = input.ident.clone();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
where_token: Default::default(),
predicates: Default::default(),
});
where_clause.predicates.push(
syn::parse2(quote! {
Self: 'static + #FQSend + #FQSync + #FQClone + ::core::cmp::Eq + ::core::fmt::Debug + ::core::hash::Hash
})
.unwrap(),
);
quote! {
const _: () = {
extern crate alloc;
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
fn dyn_clone(&self) -> alloc::boxed::Box<dyn #trait_path> {
alloc::boxed::Box::new(#FQClone::clone(self))
}
}
};
}
.into()
}
pub fn pascal_to_snake_case(s: &str) -> String {
let mut out = String::new();
let chars: Vec<char> = s.chars().collect();
for (i, &ch) in chars.iter().enumerate() {
if ch.is_uppercase() {
let prev_is_lower = i > 0 && chars[i - 1].is_lowercase();
let next_is_lower = chars.get(i + 1).is_some_and(|c| c.is_lowercase());
if i > 0 && (prev_is_lower || next_is_lower) {
out.push('_');
}
out.push(ch.to_lowercase().next().unwrap());
} else {
out.push(ch);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pascal_to_snake_case() {
assert_eq!(pascal_to_snake_case("PascalCase"), "pascal_case");
assert_eq!(pascal_to_snake_case("lowercase"), "lowercase");
assert_eq!(pascal_to_snake_case("HTTPServer"), "http_server");
}
}