//! 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 MyTrait for MyType { ... } /// /// // Generate only Sendable impl /// #[future_form(Sendable)] /// impl MyTrait for MyType { ... } /// /// // Generate only Local impl /// #[future_form(Local)] /// impl MyTrait for MyType { ... } /// /// // Add bounds only for specific variants /// #[future_form(Sendable where T: Send, Local)] /// impl MyTrait for Container { ... } /// // Generates: impl MyTrait for Container /// // impl MyTrait for Container /// ``` /// /// # Example /// /// ```rust,ignore /// use std::marker::PhantomData; /// use future_form::{FutureForm, Sendable, Local, future_form}; /// /// trait Counter { /// fn next(&self) -> F::Future<'_, u32>; /// } /// /// struct Memory { /// val: u32, /// _marker: PhantomData, /// } /// /// #[future_form(Sendable, Local)] /// impl Counter for Memory { /// 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::(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 { 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 conditions fn parse_kinds(attr: &TokenStream) -> Result, TokenStream> { use proc_macro2::TokenTree; let attr2: TokenStream2 = attr.clone().into(); let tokens: Vec = 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>`) /// 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 conditions fn collect_where_clause( tokens: &[proc_macro2::TokenTree], start: usize, span: proc_macro2::Span, ) -> Result<(Vec, usize), TokenStream> { use proc_macro2::TokenTree; let mut i = start; let mut current_predicate_tokens: Vec = 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 { 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::(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 { // 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 = 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, /// Additional where clause bounds for this variant extra_bounds: Vec, } /// 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, } 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 { let mut candidates: Vec = 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 = 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 = 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` → `S: AsyncSigner` 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 = 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` into `S: AsyncSigner`, /// `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, ) -> 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 fine fn replace_ident_in_type( ty: &Type, from: &Ident, kind_path: &Path, future_path: &Option, ) -> 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 { 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, ) -> 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 }