Something went wrong. Try again.
๐ฆ๐ Abstract over `Send` and `!Send` traits crates.io/crates/future_form
Something went wrong. Try again.
34 kB ยท 973 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974//! Proc macros for `future_form`.//!//! This crate provides the `#[future_form]` attribute macro.
use proc_macro::TokenStream;use proc_macro2::TokenStream as TokenStream2;use quote::quote;use syn::{ GenericParam, Ident, ImplItem, ItemImpl, Path, Type, WherePredicate, parse_quote, visit::Visit, visit_mut::{self, VisitMut},};
/// Generate implementations of a trait for `Sendable` and/or `Local` `FutureForm`s.////// This attribute macro allows you to write a single implementation that works for both/// `Send` and `!Send` futures, avoiding code duplication.////// # Usage////// ```rust,ignore/// // Generate both Sendable and Local impls/// #[future_form(Sendable, Local)]/// impl<F: FutureForm> MyTrait<F> for MyType<F> { ... }////// // Generate only Sendable impl/// #[future_form(Sendable)]/// impl<F: FutureForm> MyTrait<F> for MyType<F> { ... }////// // Generate only Local impl/// #[future_form(Local)]/// impl<F: FutureForm> MyTrait<F> for MyType<F> { ... }////// // Add bounds only for specific variants/// #[future_form(Sendable where T: Send, Local)]/// impl<F: FutureForm, T: Clone> MyTrait<F> for Container<T> { ... }/// // Generates: impl<T: Clone + Send> MyTrait<Sendable> for Container<T>/// // impl<T: Clone> MyTrait<Local> for Container<T>/// ```////// # Example////// ```rust,ignore/// use std::marker::PhantomData;/// use future_form::{FutureForm, Sendable, Local, future_form};////// trait Counter<F: FutureForm> {/// fn next(&self) -> F::Future<'_, u32>;/// }////// struct Memory<F> {/// val: u32,/// _marker: PhantomData<F>,/// }////// #[future_form(Sendable, Local)]/// impl<F: FutureForm> Counter<F> for Memory<F> {/// fn next(&self) -> F::Future<'_, u32> {/// let val = self.val;/// F::from_future(async move { val + 1 })/// }/// }/// ```#[proc_macro_attribute]pub fn future_form(attr: TokenStream, item: TokenStream) -> TokenStream { let Ok(input) = syn::parse::<ItemImpl>(item.clone()) else { let item2: TokenStream2 = item.into(); let msg = detect_non_impl_item(&item2); return make_error(proc_macro2::Span::call_site(), &msg); };
let kinds = match parse_kinds(&attr) { Ok(k) => k, Err(err) => return err, };
match generate_impls(&input, &kinds) { Ok(tokens) => tokens.into(), Err(err) => err.to_compile_error().into(), }}
/// Detects what kind of item was annotated and returns a helpful error message.fn detect_non_impl_item(tokens: &TokenStream2) -> String { use proc_macro2::TokenTree;
let first_ident = tokens.clone().into_iter().find_map(|tt| { if let TokenTree::Ident(ident) = tt { Some(ident.to_string()) } else { None } });
match first_ident.as_deref() { Some("trait") => { "#[future_form] can only be applied to impl blocks, not trait definitions; \ define your trait normally with a `K: FutureForm` parameter, then use \ #[future_form] on the impl blocks" .to_string() } Some("fn" | "async") => { "#[future_form] can only be applied to impl blocks, not free functions; \ write a trait with `K: FutureForm` and apply #[future_form] to its impl" .to_string() } Some("struct") => { "#[future_form] can only be applied to impl blocks, not struct definitions".to_string() } Some("enum") => { "#[future_form] can only be applied to impl blocks, not enum definitions".to_string() } Some("mod") => "#[future_form] can only be applied to impl blocks, not modules".to_string(), _ => "#[future_form] can only be applied to impl blocks".to_string(), }}
/// Creates an error `TokenStream` with proper span information.fn make_error(span: proc_macro2::Span, msg: &str) -> TokenStream { syn::Error::new(span, msg).to_compile_error().into()}
/// Returns the concrete future path for built-in variants, or None for custom types.fn builtin_future_path(ident: &Ident) -> Option<Path> { if ident == "Sendable" { Some(parse_quote!(::futures::future::BoxFuture)) } else if ident == "Local" { Some(parse_quote!(::futures::future::LocalBoxFuture)) } else { None }}
/// Checks if an ident could be a variant name.////// Variants are type names like `Sendable`, `Local`, or custom `FutureForm` types./// Excludes: `where`, single-letter idents (likely type params like T, K, F),/// and common trait names that appear in bounds.fn is_likely_variant(ident: &Ident) -> bool { let s = ident.to_string();
// Must start uppercase if !s.chars().next().is_some_and(char::is_uppercase) { return false; }
// Exclude keywords if s == "where" || s == "Self" { return false; }
// Exclude single-letter idents (likely type parameters: T, K, F, U, etc.) if s.len() == 1 { return false; }
// Exclude common trait names that appear in bounds let common_traits = [ "Send", "Sync", "Clone", "Copy", "Debug", "Display", "Default", "Fn", "FnMut", "FnOnce", "Future", "Iterator", "IntoIterator", "From", "Into", "TryFrom", "TryInto", "AsRef", "AsMut", "Eq", "PartialEq", "Ord", "PartialOrd", "Hash", "Sized", "Unpin", "Drop", ]; if common_traits.contains(&s.as_str()) { return false; }
true}
/// Suggests the correct built-in variant name for a misspelled or incorrect ident.////// Returns a hint string like `"; did you mean `Sendable`?"` or empty if no suggestion.fn suggest_variant(ident: &str) -> String { let lower = ident.to_lowercase();
// Case-insensitive match for built-in variants if lower == "sendable" { return "; did you mean `Sendable`?".to_string(); } if lower == "local" { return "; did you mean `Local`?".to_string(); } // Common confusions: trait names that are close to variant names match ident { "Send" | "BoxFuture" => "; did you mean `Sendable`?".to_string(), "LocalBoxFuture" => "; did you mean `Local`?".to_string(), "HostSendable" | "HostLocal" | "HostDriven" | "HostFuture" => { "; host-driven variants were removed โ use `Sendable` or `Local` with `future_form_ffi::poll_once::PollOnce` instead".to_string() } _ => String::new(), }}
/// Parses the attribute tokens into a list of `FutureFormVariant`s.////// Uses token-based parsing to correctly handle all delimiters (parentheses,/// brackets, braces, angle brackets) in where clause bounds.#[allow(clippy::expect_used)] // Indexing is bounds-checked by loop conditionsfn parse_kinds(attr: &TokenStream) -> Result<Vec<FutureFormVariant>, TokenStream> { use proc_macro2::TokenTree;
let attr2: TokenStream2 = attr.clone().into(); let tokens: Vec<TokenTree> = attr2.into_iter().collect();
if tokens.is_empty() { return Err(make_error( proc_macro2::Span::call_site(), "missing FutureForm variants: expected #[future_form(Sendable)], #[future_form(Local)], or #[future_form(Sendable, Local)]", )); }
let mut kinds = Vec::new(); let mut i = 0;
while i < tokens.len() { // Skip leading commas while i < tokens.len() { if let TokenTree::Punct(p) = tokens.get(i).expect("bounds checked") && p.as_char() == ',' { i += 1; continue; } break; }
if i >= tokens.len() { break; }
// Expect a variant name (any FutureForm type: Sendable, Local, or custom) let (kind_path, future_path, variant_span) = match tokens.get(i).expect("bounds checked") { TokenTree::Ident(ident) => { if is_likely_variant(ident) { let future = builtin_future_path(ident); let path: Path = parse_quote!(#ident); (path, future, ident.span()) } else { let hint = suggest_variant(&ident.to_string()); return Err(make_error( ident.span(), &format!("expected FutureForm variant, found `{ident}`{hint}"), )); } } other @ (TokenTree::Group(_) | TokenTree::Punct(_) | TokenTree::Literal(_)) => { return Err(make_error( other.span(), "expected FutureForm variant (e.g., `Sendable`, `Local`, or custom type)", )); } }; i += 1;
// Check for optional `where` clause let extra_bounds = if i < tokens.len() { if let TokenTree::Ident(ident) = tokens.get(i).expect("bounds checked") { if ident == "where" { i += 1; // Collect tokens until we hit another variant name or end let (predicates, new_i) = collect_where_clause(&tokens, i, variant_span)?; i = new_i; predicates } else { vec![] } } else { vec![] } } else { vec![] };
kinds.push(FutureFormVariant { kind_path, future_path, extra_bounds, }); }
if kinds.is_empty() { return Err(make_error( proc_macro2::Span::call_site(), "missing FutureForm variants: expected #[future_form(Sendable)], #[future_form(Local)], or #[future_form(Sendable, Local)]", )); }
// Detect duplicate variants let mut seen: Vec<&Path> = Vec::new(); for kind in &kinds { if let Some(dup) = seen.iter().find(|p| paths_ident_equal(p, &kind.kind_path)) { let dup_name = dup .segments .last() .map_or_else(|| "unknown".to_string(), |s| s.ident.to_string()); return Err(make_error( kind.kind_path .segments .last() .map_or_else(proc_macro2::Span::call_site, |s| s.ident.span()), &format!( "duplicate FutureForm variant `{dup_name}`; each variant may only appear once" ), )); } seen.push(&kind.kind_path); }
Ok(kinds)}
/// Collects where clause tokens until hitting another variant name or end.////// Tracks angle bracket depth so that commas inside `<>` (e.g., `T: Into<Vec<u8>>`)/// are not mistaken for predicate or variant separators. Note that `<>` are not/// `Group` delimiters in `proc_macro2` โ they appear as individual `Punct` tokens.////// Returns the parsed predicates and the new token index.#[allow(clippy::expect_used)] // Indexing is bounds-checked by loop conditionsfn collect_where_clause( tokens: &[proc_macro2::TokenTree], start: usize, span: proc_macro2::Span,) -> Result<(Vec<WherePredicate>, usize), TokenStream> { use proc_macro2::TokenTree;
let mut i = start; let mut current_predicate_tokens: Vec<TokenTree> = Vec::new(); let mut predicates = Vec::new(); // Track whether we're after a `:` (inside a type bound) โ variants only appear after `,` let mut in_bound = false; // Track angle bracket nesting depth so commas inside `<>` are not top-level let mut angle_depth: u32 = 0;
while i < tokens.len() { let token = tokens.get(i).expect("bounds checked");
// Track angle bracket depth if let TokenTree::Punct(p) = token { match p.as_char() { '<' => angle_depth = angle_depth.saturating_add(1), '>' => angle_depth = angle_depth.saturating_sub(1), ':' if angle_depth == 0 => in_bound = true, ',' if angle_depth == 0 => in_bound = false, _ => {} } }
let at_top_level = angle_depth == 0;
// Check if this is a variant name (end of where clause) // Only check at the START of a predicate (not inside a bound or angle brackets) // Must NOT be followed by `:` (which would make it a type param bound, not a variant) if at_top_level && !in_bound && current_predicate_tokens.is_empty() && let TokenTree::Ident(ident) = token && is_likely_variant(ident) && !tokens .get(i + 1) .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':')) { return Ok((predicates, i)); }
// Check for comma at top level (predicate separator) if at_top_level && let TokenTree::Punct(p) = token && p.as_char() == ',' { // Check if next token is a variant name (starts a new variant, not a predicate) let next_is_variant = tokens.get(i + 1).is_some_and(|t| { if let TokenTree::Ident(id) = t { // It's a variant if it looks like one AND is not followed by `:` is_likely_variant(id) && !tokens.get(i + 2).is_some_and( |t2| matches!(t2, TokenTree::Punct(p2) if p2.as_char() == ':'), ) } else { false } });
if next_is_variant { // This comma separates variants, not predicates if !current_predicate_tokens.is_empty() { predicates.push(parse_predicate_tokens(¤t_predicate_tokens, span)?); } i += 1; // Skip the comma return Ok((predicates, i)); } // This comma separates predicates within the where clause if !current_predicate_tokens.is_empty() { predicates.push(parse_predicate_tokens(¤t_predicate_tokens, span)?); current_predicate_tokens.clear(); } i += 1; continue; }
// Add token to current predicate current_predicate_tokens.push(token.clone()); i += 1; }
// End of tokens โ parse any remaining predicate if !current_predicate_tokens.is_empty() { predicates.push(parse_predicate_tokens(¤t_predicate_tokens, span)?); }
Ok((predicates, i))}
/// Parses a sequence of tokens as a where predicate.////// Uses the span of the first token in the predicate for error reporting,/// so errors point at the malformed predicate rather than the variant name./// When the last token looks like a variant name, suggests a missing comma.fn parse_predicate_tokens( tokens: &[proc_macro2::TokenTree], fallback_span: proc_macro2::Span,) -> Result<WherePredicate, TokenStream> { use proc_macro2::TokenTree;
let token_stream: TokenStream2 = tokens.iter().cloned().collect(); let token_str = token_stream.to_string(); let span = tokens.first().map_or(fallback_span, TokenTree::span);
syn::parse2::<WherePredicate>(token_stream).map_err(|e| { // Check if the last token looks like a variant name that was absorbed // due to a missing comma (e.g., `T: Send Local` instead of `T: Send, Local`) let comma_hint = tokens.last().and_then(|last| { if let TokenTree::Ident(ident) = last && is_likely_variant(ident) { Some(format!("; did you forget a comma before `{ident}`?")) } else { None } });
let hint = comma_hint.unwrap_or_default(); make_error( span, &format!("malformed where clause `{token_str}`: {e}{hint}"), ) })}
fn generate_impls(input: &ItemImpl, kinds: &[FutureFormVariant]) -> syn::Result<TokenStream2> { // Reject inherent impls โ #[future_form] only works on trait impls if input.trait_.is_none() { return Err(syn::Error::new_spanned( &input.self_ty, "#[future_form] can only be applied to trait impls, not inherent impls; \ move these methods into a trait and apply #[future_form] to the trait impl", )); }
// Find the K type parameter let k_param = find_k_param(input)?;
// Check for async fn methods (unsupported โ must use K::from_future pattern) check_for_async_fn(input, &k_param)?;
// Warn if the FutureForm parameter is never referenced in any impl item check_k_usage(input, &k_param)?;
// Generate impl for each requested kind let impls: Vec<TokenStream2> = kinds .iter() .map(|kind| generate_impl_for_kind(input, &k_param, kind)) .collect();
Ok(quote! { #(#impls)* })}
/// Checks for `async fn` methods in the impl block, which are not supported.////// Users must use `K::from_future(async { ... })` instead of `async fn` because/// the macro cannot desugar `async fn` into the correct boxed future type.fn check_for_async_fn(input: &ItemImpl, k_param: &Ident) -> syn::Result<()> { for item in &input.items { if let ImplItem::Fn(method) = item && method.sig.asyncness.is_some() { return Err(syn::Error::new_spanned( method.sig.asyncness, format!( "`async fn` is not supported in #[future_form] impl blocks; \ return `{k_param}::Future<'_, T>` and use \ `{k_param}::from_future(async {{ ... }})` instead" ), )); } } Ok(())}
/// Checks that the `FutureForm` parameter is actually referenced in at least one impl item.////// If no method, associated type, or const references `K`, the `#[future_form]` attribute/// is likely applied by mistake.fn check_k_usage(input: &ItemImpl, k_param: &Ident) -> syn::Result<()> { // Check trait path (K may appear in the trait being implemented) if let Some((_, ref trait_path, _)) = input.trait_ { let mut finder = IdentFinder { target: k_param.clone(), found: false, }; finder.visit_path(trait_path); if finder.found { return Ok(()); } }
// Check self type { let mut finder = IdentFinder { target: k_param.clone(), found: false, }; finder.visit_type(&input.self_ty); if finder.found { return Ok(()); } }
// Check impl items (methods, associated types, consts) for item in &input.items { let mut finder = IdentFinder { target: k_param.clone(), found: false, }; finder.visit_impl_item(item); if finder.found { return Ok(()); } }
Err(syn::Error::new( k_param.span(), format!( "the FutureForm parameter `{k_param}` is not referenced in this impl block; \ #[future_form] may not be needed here" ), ))}
#[derive(Clone)]struct FutureFormVariant { /// Path to the `FutureForm` implementor (e.g., `Sendable`, `Local`, `MyCustomForm`) kind_path: Path, /// Concrete future path for built-in types (e.g., `BoxFuture`), None for custom types future_path: Option<Path>, /// Additional where clause bounds for this variant extra_bounds: Vec<WherePredicate>,}
/// Visitor that replaces standalone `K` type parameter references with concrete paths.////// Only replaces `Path` nodes where the first segment is exactly the target ident/// (e.g., `K`, `K::Future`, `K::from_future`), not idents containing it as a substring.struct KindReplacer { from_ident: Ident, to_path: Path, /// Concrete future path for built-in types (`BoxFuture`, `LocalBoxFuture`). /// When `None` (custom types), `K::Future` becomes `CustomType::Future`. future_path: Option<Path>,}
impl VisitMut for KindReplacer { fn visit_path_mut(&mut self, path: &mut Path) { // First, recurse into nested paths (generic arguments, etc.) visit_mut::visit_path_mut(self, path);
// Check if first segment is exactly our target ident if let Some(first) = path.segments.first() && first.ident == self.from_ident && first.arguments.is_empty() { if path.segments.len() == 1 { // Standalone K โ replace with the target type *path = self.to_path.clone(); } else if let Some(second) = path.segments.get(1) { // K::Future<...> or K::from_future(...) if second.ident == "Future" { if let Some(ref concrete_future) = self.future_path { // Built-in: K::Future<'a, T> โ BoxFuture<'a, T> let args = second.arguments.clone(); let mut new_path = concrete_future.clone(); if let Some(last) = new_path.segments.last_mut() { last.arguments = args; } *path = new_path; } else { // Custom: K::Future<'a, T> โ CustomType::Future<'a, T> let remaining: Vec<_> = path.segments.iter().skip(1).cloned().collect(); let mut new_path = self.to_path.clone(); new_path.segments.extend(remaining); *path = new_path; } } else { // K::from_future โ Type::from_future let remaining: Vec<_> = path.segments.iter().skip(1).cloned().collect(); let mut new_path = self.to_path.clone(); new_path.segments.extend(remaining); *path = new_path; } } } }}
/// Visitor that checks if a where predicate references a specific ident as a standalone type.struct IdentFinder { target: Ident, found: bool,}
impl<'ast> Visit<'ast> for IdentFinder { fn visit_path(&mut self, path: &'ast Path) { // Only match standalone K, not as part of larger identifiers if let Some(first) = path.segments.first() && path.segments.len() == 1 && first.ident == self.target && first.arguments.is_empty() { self.found = true; } syn::visit::visit_path(self, path); }}
fn find_k_param(input: &ItemImpl) -> syn::Result<Ident> { let mut candidates: Vec<Ident> = Vec::new();
// Look for type parameters that have FutureForm bound (inline) for param in &input.generics.params { if let GenericParam::Type(type_param) = param { for bound in &type_param.bounds { if let syn::TypeParamBound::Trait(trait_bound) = bound { let path = &trait_bound.path; if path .segments .last() .is_some_and(|s| s.ident == "FutureForm") { candidates.push(type_param.ident.clone()); } } } } }
// Also check the where clause for `K: FutureForm` style bounds if let Some(ref where_clause) = input.generics.where_clause { for pred in &where_clause.predicates { if let WherePredicate::Type(type_pred) = pred { let has_future_form = type_pred.bounds.iter().any(|bound| { if let syn::TypeParamBound::Trait(trait_bound) = bound { trait_bound .path .segments .last() .is_some_and(|s| s.ident == "FutureForm") } else { false } }); if has_future_form && let Type::Path(type_path) = &type_pred.bounded_ty && type_path.qself.is_none() && type_path.path.segments.len() == 1 && let Some(seg) = type_path.path.segments.first() && seg.arguments.is_empty() && !candidates.contains(&seg.ident) { candidates.push(seg.ident.clone()); } } } }
match candidates.len() { 0 => Err(syn::Error::new_spanned( &input.generics, "expected a type parameter with FutureForm bound (e.g., `K: FutureForm`)", )), 1 => Ok(candidates.swap_remove(0)), _ => { let names: Vec<String> = candidates.iter().map(ToString::to_string).collect(); Err(syn::Error::new_spanned( &input.generics, format!( "found multiple type parameters with FutureForm bound ({}); \ only one FutureForm parameter is supported", names.join(", ") ), )) } }}
fn generate_impl_for_kind( input: &ItemImpl, k_param: &Ident, variant: &FutureFormVariant,) -> TokenStream2 { let kind_path: Path = variant.kind_path.clone(); let future_path: Option<Path> = variant.future_path.clone();
// Clone and modify generics - remove the K parameter let mut new_generics = input.generics.clone(); new_generics.params = new_generics .params .into_iter() .filter(|p| { if let GenericParam::Type(tp) = p { tp.ident != *k_param } else { true } }) .collect();
// Rewrite K references in remaining params' inline bounds. // e.g., `S: AsyncSigner<K>` โ `S: AsyncSigner<Sendable>` let mut param_replacer = KindReplacer { from_ident: k_param.clone(), to_path: kind_path.clone(), future_path: future_path.clone(), }; for param in &mut new_generics.params { if let GenericParam::Type(tp) = param { param_replacer.visit_type_param_mut(tp); } }
// Rewrite where clause predicates that reference K, and add extra bounds if let Some(ref mut where_clause) = new_generics.where_clause { where_clause.predicates = where_clause .predicates .clone() .into_iter() .filter_map(|pred| { if predicate_references_ident(&pred, k_param) { // Rewrite K references in this predicate, then keep it // unless it becomes a tautological bound like `Sendable: FutureForm` let rewritten = rewrite_predicate(&pred, k_param, &kind_path, &future_path); if is_tautological_predicate(&rewritten, &kind_path) { None } else { Some(rewritten) } } else { Some(pred) } }) .collect(); // Add variant-specific extra bounds for bound in &variant.extra_bounds { where_clause.predicates.push(bound.clone()); } } else if !variant.extra_bounds.is_empty() { // Create a where clause if we have extra bounds but none existed let mut predicates = syn::punctuated::Punctuated::new(); for bound in &variant.extra_bounds { predicates.push(bound.clone()); } new_generics.where_clause = Some(syn::WhereClause { where_token: syn::token::Where::default(), predicates, }); }
// Replace K in self_ty let new_self_ty = replace_ident_in_type(&input.self_ty, k_param, &kind_path, &future_path);
// Replace K in trait path if present let new_trait = input.trait_.as_ref().map(|(bang, path, for_token)| { let new_path = replace_ident_in_path(path, k_param, &kind_path, &future_path); (*bang, new_path, *for_token) });
// Transform methods let new_items: Vec<ImplItem> = input .items .iter() .map(|item| transform_impl_item(item, k_param, &kind_path, &future_path)) .collect();
let (impl_generics, _, where_clause) = new_generics.split_for_impl();
let trait_tokens = new_trait.map(|(bang, path, for_token)| { quote! { #bang #path #for_token } });
quote! { impl #impl_generics #trait_tokens #new_self_ty #where_clause { #(#new_items)* } }}
fn predicate_references_ident(pred: &syn::WherePredicate, ident: &Ident) -> bool { let mut finder = IdentFinder { target: ident.clone(), found: false, }; finder.visit_where_predicate(pred); finder.found}
/// Rewrites K references in a where clause predicate.////// Transforms predicates like `S: AsyncSigner<K>` into `S: AsyncSigner<Sendable>`,/// `K::Future<'a, T>` into `BoxFuture<'a, T>`, etc.#[allow(clippy::ref_option)]fn rewrite_predicate( pred: &WherePredicate, from: &Ident, kind_path: &Path, future_path: &Option<Path>,) -> WherePredicate { let mut pred = pred.clone(); let mut replacer = KindReplacer { from_ident: from.clone(), to_path: kind_path.clone(), future_path: future_path.clone(), }; replacer.visit_where_predicate_mut(&mut pred); pred}
/// Compares two `syn::Path`s by segment idents only (ignoring generic arguments).////// This is sufficient for checking variant paths like `Sendable` or `Local`,/// but does not compare generic arguments on segments. If you need full/// structural equality, compare arguments as well.fn paths_ident_equal(a: &Path, b: &Path) -> bool { a.leading_colon.is_some() == b.leading_colon.is_some() && a.segments.len() == b.segments.len() && a.segments .iter() .zip(b.segments.iter()) .all(|(sa, sb)| sa.ident == sb.ident)}
/// Checks if a rewritten predicate is tautological and should be removed.////// After rewriting `K: FutureForm` โ `Sendable: FutureForm`, the predicate is/// trivially true and clutters the generated code. This function detects such/// predicates where the bounded type is exactly the variant type and the only/// bounds are `FutureForm` and/or `?Sized`.fn is_tautological_predicate(pred: &WherePredicate, kind_path: &Path) -> bool { let WherePredicate::Type(type_pred) = pred else { return false; }; let Type::Path(type_path) = &type_pred.bounded_ty else { return false; }; if type_path.qself.is_some() || !paths_ident_equal(&type_path.path, kind_path) { return false; } // All bounds must be FutureForm or ?Sized for the predicate to be tautological type_pred.bounds.iter().all(|bound| match bound { syn::TypeParamBound::Trait(trait_bound) => { // `?Sized` is a Trait bound with modifier `Maybe(_)` let is_maybe_sized = matches!(trait_bound.modifier, syn::TraitBoundModifier::Maybe(_)) && trait_bound .path .segments .last() .is_some_and(|s| s.ident == "Sized");
let is_future_form = trait_bound .path .segments .last() .is_some_and(|s| s.ident == "FutureForm");
is_maybe_sized || is_future_form } // Lifetime bounds, precise captures, verbatim, and future additions syn::TypeParamBound::Lifetime(_) | syn::TypeParamBound::PreciseCapture(_) | syn::TypeParamBound::Verbatim(_) | _ => false, })}
#[allow(clippy::ref_option)] // We clone the Option, so &Option is finefn replace_ident_in_type( ty: &Type, from: &Ident, kind_path: &Path, future_path: &Option<Path>,) -> Type { let mut ty = ty.clone(); let mut replacer = KindReplacer { from_ident: from.clone(), to_path: kind_path.clone(), future_path: future_path.clone(), }; replacer.visit_type_mut(&mut ty); ty}
#[allow(clippy::ref_option)]fn replace_ident_in_path( path: &Path, from: &Ident, kind_path: &Path, future_path: &Option<Path>,) -> Path { let mut path = path.clone(); let mut replacer = KindReplacer { from_ident: from.clone(), to_path: kind_path.clone(), future_path: future_path.clone(), }; replacer.visit_path_mut(&mut path); path}
/// Rewrites all K references in an impl item (methods, associated types, consts).////// Uses `KindReplacer` on the entire `ImplItem`, which covers method parameter/// types, return types, bodies, method-level generics, associated type definitions,/// and const values.#[allow(clippy::ref_option)]fn transform_impl_item( item: &ImplItem, k_param: &Ident, kind_path: &Path, future_path: &Option<Path>,) -> ImplItem { let mut item = item.clone(); let mut replacer = KindReplacer { from_ident: k_param.clone(), to_path: kind_path.clone(), future_path: future_path.clone(), }; replacer.visit_impl_item_mut(&mut item); item}