Skip to main content

wasm_bindgen_macro_support/
generics.rs

1use std::borrow::Cow;
2use std::collections::{BTreeMap, BTreeSet};
3use syn::parse_quote;
4use syn::visit_mut::{self, VisitMut};
5use syn::{visit::Visit, Ident, Type};
6
7use crate::error::Diagnostic;
8
9/// Visitor to replace wasm bindgen generics with their concrete types
10/// The concrete type is the default type on the import if specified when it was defined.
11struct GenericRenameVisitor<'a> {
12    renames: &'a BTreeMap<&'a Ident, Option<Cow<'a, syn::Type>>>,
13    err: Option<Diagnostic>,
14}
15
16impl<'a> VisitMut for GenericRenameVisitor<'a> {
17    fn visit_type_mut(&mut self, ty: &mut Type) {
18        if self.err.is_some() {
19            return;
20        }
21        if let Type::Path(type_path) = ty {
22            // Handle <T as Trait>::AssocType
23            if let Some(qself) = &mut type_path.qself {
24                if let Type::Path(qself_path) = &mut *qself.ty {
25                    if qself_path.qself.is_none() && qself_path.path.segments.len() == 1 {
26                        let ident = &qself_path.path.segments[0].ident;
27                        if let Some((_, concrete)) = self.renames.get_key_value(ident) {
28                            *qself.ty = if let Some(concrete) = concrete {
29                                concrete.clone().into_owned()
30                            } else {
31                                parse_quote! { JsValue }
32                            };
33                            return;
34                        }
35                    }
36                }
37            }
38            // Normal T::...
39            if type_path.qself.is_none() && !type_path.path.segments.is_empty() {
40                let first_seg = &type_path.path.segments[0];
41
42                if let Some((_, concrete)) = self.renames.get_key_value(&first_seg.ident) {
43                    if let Some(concrete) = concrete {
44                        if type_path.path.segments.len() == 1 {
45                            *ty = concrete.clone().into_owned();
46                        } else if let Type::Path(concrete_path) = concrete.as_ref() {
47                            let remaining: Vec<_> =
48                                type_path.path.segments.iter().skip(1).cloned().collect();
49                            type_path.path.segments = concrete_path.path.segments.clone();
50                            type_path.path.segments.extend(remaining);
51                        }
52                    } else {
53                        *ty = parse_quote! { JsValue };
54                    }
55                    return;
56                }
57            }
58        }
59        visit_mut::visit_type_mut(self, ty);
60    }
61}
62
63/// Helper visitor for generic parameter usage
64#[derive(Debug)]
65pub struct GenericNameVisitor<'a, 'b> {
66    generic_params: &'a Vec<&'a Ident>,
67    /// The generic params that were found
68    found_set: &'b mut BTreeSet<Ident>,
69}
70
71/// Helper visitor for generic parameter usage
72impl<'a, 'b> GenericNameVisitor<'a, 'b> {
73    /// Construct a new generic name visitors with a param search set,
74    /// and optionally a second parameter search set.
75    pub fn new(generic_params: &'a Vec<&'a Ident>, found_set: &'b mut BTreeSet<Ident>) -> Self {
76        Self {
77            generic_params,
78            found_set,
79        }
80    }
81}
82
83impl<'a, 'b> Visit<'a> for GenericNameVisitor<'a, 'b> {
84    fn visit_type_reference(&mut self, type_ref: &'a syn::TypeReference) {
85        if let syn::Type::Path(type_path) = &*type_ref.elem {
86            // Handle <T as Trait>::AssocType - visit the qself type
87            if let Some(qself) = &type_path.qself {
88                syn::visit::visit_type(self, &qself.ty);
89                // Also visit the path segments for any generic args
90                for segment in &type_path.path.segments {
91                    syn::visit::visit_path_segment(self, segment);
92                }
93                return;
94            }
95
96            if let Some(first_segment) = type_path.path.segments.first() {
97                if type_path.path.segments.len() == 1 && first_segment.arguments.is_empty() {
98                    if self.generic_params.contains(&&first_segment.ident) {
99                        self.found_set.insert(first_segment.ident.clone());
100                        return;
101                    }
102                } else {
103                    if self.generic_params.contains(&&first_segment.ident) {
104                        self.found_set.insert(first_segment.ident.clone());
105                    }
106
107                    syn::visit::visit_path_arguments(self, &first_segment.arguments);
108
109                    for segment in type_path.path.segments.iter().skip(1) {
110                        syn::visit::visit_path_segment(self, segment);
111                    }
112                    return;
113                }
114            }
115        }
116
117        // For other cases, continue normal visiting
118        syn::visit::visit_type_reference(self, type_ref);
119    }
120
121    fn visit_path(&mut self, path: &'a syn::Path) {
122        if let Some(first_segment) = path.segments.first() {
123            if self.generic_params.contains(&&first_segment.ident) {
124                self.found_set.insert(first_segment.ident.clone());
125            }
126        }
127
128        for segment in &path.segments {
129            match &segment.arguments {
130                syn::PathArguments::AngleBracketed(args) => {
131                    for arg in &args.args {
132                        match arg {
133                            syn::GenericArgument::Type(ty) => {
134                                syn::visit::visit_type(self, ty);
135                            }
136                            syn::GenericArgument::AssocType(binding) => {
137                                // Don't visit binding.ident, only visit binding.ty
138                                syn::visit::visit_type(self, &binding.ty);
139                            }
140                            _ => {
141                                syn::visit::visit_generic_argument(self, arg);
142                            }
143                        }
144                    }
145                }
146                syn::PathArguments::Parenthesized(args) => {
147                    // Handle function syntax like FnMut(T) -> Result<R, JsValue>
148                    for input in &args.inputs {
149                        syn::visit::visit_type(self, input);
150                    }
151                    if let syn::ReturnType::Type(_, return_type) = &args.output {
152                        syn::visit::visit_type(self, return_type);
153                    }
154                }
155                syn::PathArguments::None => {}
156            }
157        }
158    }
159}
160
161/// Obtain the generic parameters and their optional defaults
162pub(crate) fn generic_params(generics: &syn::Generics) -> Vec<(&Ident, Option<&syn::Type>)> {
163    generics
164        .type_params()
165        .map(|tp| (&tp.ident, tp.default.as_ref()))
166        .collect()
167}
168
169/// Returns a vector of token streams representing generic type parameters with their bounds.
170/// For example, `<T: Clone, U: Display>` returns `[quote!(T: Clone), quote!(U: Display)]`.
171/// This is useful for constructing impl blocks that need to add lifetimes while preserving bounds.
172pub(crate) fn type_params_with_bounds(generics: &syn::Generics) -> Vec<proc_macro2::TokenStream> {
173    generics
174        .type_params()
175        .map(|tp| {
176            let ident = &tp.ident;
177            let bounds = &tp.bounds;
178            if bounds.is_empty() {
179                quote::quote! { #ident }
180            } else {
181                quote::quote! { #ident: #bounds }
182            }
183        })
184        .collect()
185}
186/// Obtain the generic bounds, both inline and where clauses together
187pub(crate) fn generic_bounds<'a>(generics: &'a syn::Generics) -> Vec<Cow<'a, syn::WherePredicate>> {
188    let mut bounds = Vec::new();
189    for param in &generics.params {
190        if let syn::GenericParam::Type(type_param) = param {
191            if !type_param.bounds.is_empty() {
192                let ident = &type_param.ident;
193                let predicate = syn::WherePredicate::Type(syn::PredicateType {
194                    lifetimes: None,
195                    bounded_ty: syn::parse_quote!(#ident),
196                    colon_token: syn::Token![:](proc_macro2::Span::call_site()),
197                    bounds: type_param.bounds.clone(),
198                });
199                bounds.push(Cow::Owned(predicate));
200            }
201        }
202    }
203    if let Some(where_clause) = &generics.where_clause {
204        bounds.extend(where_clause.predicates.iter().map(Cow::Borrowed));
205    }
206    bounds
207}
208
209/// Replace specified lifetime parameters with 'static.
210/// This is used when generating concrete ABI types for extern blocks,
211/// which cannot have lifetime parameters from the outer scope.
212/// Only the lifetimes in `lifetimes_to_staticize` are replaced.
213pub(crate) fn staticize_lifetimes(
214    mut ty: syn::Type,
215    lifetimes_to_staticize: &[&syn::Lifetime],
216) -> syn::Type {
217    struct LifetimeStaticizer<'a> {
218        lifetimes: &'a [&'a syn::Lifetime],
219    }
220    impl VisitMut for LifetimeStaticizer<'_> {
221        fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) {
222            if self.lifetimes.iter().any(|lt| lt.ident == lifetime.ident) {
223                *lifetime = syn::Lifetime::new("'static", lifetime.span());
224            }
225        }
226    }
227    LifetimeStaticizer {
228        lifetimes: lifetimes_to_staticize,
229    }
230    .visit_type_mut(&mut ty);
231    ty
232}
233
234/// Obtain the generic type parameter names
235pub(crate) fn generic_param_names(generics: &syn::Generics) -> Vec<&Ident> {
236    generics.type_params().map(|tp| &tp.ident).collect()
237}
238
239/// Obtain all lifetime parameters from generics
240pub(crate) fn lifetime_params(generics: &syn::Generics) -> Vec<&syn::Lifetime> {
241    generics.lifetimes().map(|lp| &lp.lifetime).collect()
242}
243
244/// Obtain both lifetime and type parameter names from generics
245pub(crate) fn all_param_names(generics: &syn::Generics) -> (Vec<&syn::Lifetime>, Vec<&Ident>) {
246    (lifetime_params(generics), generic_param_names(generics))
247}
248
249/// Helper visitor for lifetime usage detection in types
250pub struct LifetimeVisitor<'a> {
251    lifetime_params: &'a [&'a syn::Lifetime],
252    found_set: BTreeSet<syn::Lifetime>,
253}
254
255impl<'a> LifetimeVisitor<'a> {
256    pub fn new(lifetime_params: &'a [&'a syn::Lifetime]) -> Self {
257        Self {
258            lifetime_params,
259            found_set: BTreeSet::new(),
260        }
261    }
262
263    pub fn into_found(self) -> BTreeSet<syn::Lifetime> {
264        self.found_set
265    }
266}
267
268impl<'ast> syn::visit::Visit<'ast> for LifetimeVisitor<'_> {
269    fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
270        if self.lifetime_params.contains(&lifetime) {
271            self.found_set.insert(lifetime.clone());
272        }
273    }
274}
275
276/// Find all lifetimes from the given set that are used in a type
277pub(crate) fn used_lifetimes_in_type<'a>(
278    ty: &syn::Type,
279    lifetime_params: &'a [&'a syn::Lifetime],
280) -> BTreeSet<syn::Lifetime> {
281    let mut visitor = LifetimeVisitor::new(lifetime_params);
282    syn::visit::Visit::visit_type(&mut visitor, ty);
283    visitor.into_found()
284}
285
286pub(crate) fn uses_generic_params(ty: &syn::Type, generic_names: &Vec<&Ident>) -> bool {
287    let mut found_set = Default::default();
288    let mut visitor = GenericNameVisitor::new(generic_names, &mut found_set);
289    visitor.visit_type(ty);
290    !found_set.is_empty()
291}
292
293pub(crate) fn uses_lifetime_params(ty: &syn::Type, lifetime_params: &[&syn::Lifetime]) -> bool {
294    !used_lifetimes_in_type(ty, lifetime_params).is_empty()
295}
296
297/// Find all lifetimes from the given set that are used in type param bounds
298pub(crate) fn used_lifetimes_in_bounds<'a>(
299    bounds: &syn::punctuated::Punctuated<syn::TypeParamBound, syn::token::Plus>,
300    lifetime_params: &'a [&'a syn::Lifetime],
301) -> BTreeSet<syn::Lifetime> {
302    let mut visitor = LifetimeVisitor::new(lifetime_params);
303    for bound in bounds {
304        syn::visit::Visit::visit_type_param_bound(&mut visitor, bound);
305    }
306    visitor.into_found()
307}
308
309pub(crate) fn used_generic_params<'a>(
310    ty: &'a syn::Type,
311    generic_names: &'a Vec<&Ident>,
312    mut used_params: BTreeSet<Ident>,
313) -> BTreeSet<Ident> {
314    let mut visitor = GenericNameVisitor::new(generic_names, &mut used_params);
315    visitor.visit_type(ty);
316    used_params
317}
318
319/// Usage visitor for generic bounds
320pub(crate) fn generics_predicate_uses(
321    predicate: &syn::WherePredicate,
322    generic_names: &Vec<&Ident>,
323) -> bool {
324    let mut found_set = Default::default();
325    let mut visitor = GenericNameVisitor::new(generic_names, &mut found_set);
326    visitor.visit_where_predicate(predicate);
327    !found_set.is_empty()
328}
329
330/// Concrete type replacement visitor application.
331/// Replaces generic type parameters with their concrete types (or JsValue if no default),
332/// and replaces specified lifetime parameters with 'static (since extern blocks cannot have
333/// lifetime parameters from the outer scope).
334pub(crate) fn generic_to_concrete<'a>(
335    mut ty: syn::Type,
336    generic_names: &BTreeMap<&'a Ident, Option<Cow<'a, syn::Type>>>,
337    lifetimes_to_staticize: &[&syn::Lifetime],
338) -> Result<syn::Type, Diagnostic> {
339    // First, replace type parameters with their concrete types
340    if !generic_names.is_empty() {
341        let mut visitor = GenericRenameVisitor {
342            renames: generic_names,
343            err: None,
344        };
345        visitor.visit_type_mut(&mut ty);
346        if let Some(err) = visitor.err {
347            return Err(err);
348        }
349    }
350    // Then, replace specified lifetimes with 'static for ABI compatibility
351    Ok(staticize_lifetimes(ty, lifetimes_to_staticize))
352}
353
354#[cfg(test)]
355mod tests {
356    #[test]
357    fn test_generic_name_visitor() {
358        let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
359        let u_ident = syn::Ident::new("U", proc_macro2::Span::call_site());
360        let generic_params = vec![&t_ident, &u_ident];
361
362        // Test T as value
363        let ty: syn::Type = syn::parse_quote!(T);
364        let mut found_set = Default::default();
365        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
366        syn::visit::visit_type(&mut visitor, &ty);
367        assert!(visitor.found_set.contains(&t_ident));
368
369        // Test &T as reference
370        let ty: syn::Type = syn::parse_quote!(&T);
371        let mut found_set = Default::default();
372        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
373        syn::visit::visit_type(&mut visitor, &ty);
374        assert!(visitor.found_set.contains(&t_ident));
375
376        // Test T<U> - both found
377        let ty: syn::Type = syn::parse_quote!(T<U>);
378        let mut found_set = Default::default();
379        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
380        syn::visit::visit_type(&mut visitor, &ty);
381        assert!(visitor.found_set.contains(&t_ident));
382        assert!(visitor.found_set.contains(&u_ident));
383
384        // Test &T<U> - both found
385        let ty: syn::Type = syn::parse_quote!(&T<U>);
386        let mut found_set = Default::default();
387        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
388        syn::visit::visit_type(&mut visitor, &ty);
389        assert!(visitor.found_set.contains(&t_ident));
390        assert!(visitor.found_set.contains(&u_ident));
391
392        // Test T::<U>::Foo - T and U found, Foo ignored
393        let ty: syn::Type = syn::parse_quote!(T::<U>::Foo);
394        let mut found_set = Default::default();
395        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
396        syn::visit::visit_type(&mut visitor, &ty);
397        assert!(visitor.found_set.contains(&t_ident));
398        assert!(visitor.found_set.contains(&u_ident));
399
400        // Test Vec<T> - T found, Vec ignored
401        let ty: syn::Type = syn::parse_quote!(Vec<T>);
402        let mut found_set = Default::default();
403        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
404        syn::visit::visit_type(&mut visitor, &ty);
405        assert!(visitor.found_set.contains(&t_ident));
406        assert!(!visitor.found_set.contains(&u_ident));
407    }
408
409    #[test]
410    fn test_associated_type_binding() {
411        let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
412        let u_ident = syn::Ident::new("U", proc_macro2::Span::call_site());
413        let generic_params = vec![&t_ident, &u_ident];
414
415        // Test SomeTrait<T = U> - should find U (RHS) but NOT T (LHS assoc type name)
416        let ty: syn::Type = syn::parse_quote!(SomeTrait<T = U>);
417        let mut found_set = Default::default();
418        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
419        syn::visit::visit_type(&mut visitor, &ty);
420        assert!(!visitor.found_set.contains(&t_ident)); // T is LHS assoc type name
421        assert!(visitor.found_set.contains(&u_ident)); // U is RHS generic parameter
422
423        // Test SomeTrait<U = T> - should find T (RHS) but NOT U (LHS assoc type name)
424        let ty: syn::Type = syn::parse_quote!(SomeTrait<U = T>);
425        let mut found_set = Default::default();
426        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
427        syn::visit::visit_type(&mut visitor, &ty);
428        assert!(visitor.found_set.contains(&t_ident)); // T is RHS generic parameter
429        assert!(!visitor.found_set.contains(&u_ident)); // U is LHS assoc type name
430    }
431
432    #[test]
433    fn test_nested_references() {
434        let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
435        let u_ident = syn::Ident::new("U", proc_macro2::Span::call_site());
436        let generic_params = vec![&t_ident, &u_ident];
437
438        // Test &T
439        let ty: syn::Type = syn::parse_quote!(&T);
440        let mut found_set = Default::default();
441        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
442        syn::visit::visit_type(&mut visitor, &ty);
443        assert!(visitor.found_set.contains(&t_ident));
444
445        // Test &&T
446        let ty: syn::Type = syn::parse_quote!(&&T);
447        let mut found_set = Default::default();
448        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
449        syn::visit::visit_type(&mut visitor, &ty);
450        assert!(visitor.found_set.contains(&t_ident));
451
452        // Test &&&T
453        let ty: syn::Type = syn::parse_quote!(&&&T);
454        let mut found_set = Default::default();
455        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
456        syn::visit::visit_type(&mut visitor, &ty);
457        assert!(visitor.found_set.contains(&t_ident));
458
459        // Test &T<U>
460        let ty: syn::Type = syn::parse_quote!(&T<U>);
461        let mut found_set = Default::default();
462        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
463        syn::visit::visit_type(&mut visitor, &ty);
464        assert!(visitor.found_set.contains(&t_ident));
465        assert!(visitor.found_set.contains(&u_ident));
466    }
467
468    #[test]
469    fn test_mixed_usage() {
470        let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
471        let generic_params = vec![&t_ident];
472
473        // Test T appearing in multiple places
474        let ty: syn::Type = syn::parse_quote!(SomeTrait<Item = T> + OtherTrait<Ref = &T>);
475        let mut found_set = Default::default();
476        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
477        syn::visit::visit_type(&mut visitor, &ty);
478        assert!(visitor.found_set.contains(&t_ident));
479    }
480
481    #[test]
482    fn test_ref_qself_trait_assoc_type() {
483        let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
484        let generic_params = vec![&t_ident];
485
486        // Test &<T as JsFunction1>::Arg1 - T should be found
487        let ty: syn::Type = syn::parse_quote!(&<T as JsFunction1>::Arg1);
488        let mut found_set = Default::default();
489        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
490        syn::visit::visit_type(&mut visitor, &ty);
491        assert!(
492            visitor.found_set.contains(&t_ident),
493            "T should be found in &<T as JsFunction1>::Arg1"
494        );
495    }
496
497    #[test]
498    fn test_complex_reference_with_closure() {
499        let t_ident = syn::Ident::new("T", proc_macro2::Span::call_site());
500        let r_ident = syn::Ident::new("R", proc_macro2::Span::call_site());
501        let generic_params = vec![&t_ident, &r_ident];
502
503        let ty: syn::Type = syn::parse_quote!(&Closure<dyn FnMut(T) -> Result<R, JsValue>>);
504
505        let mut found_set = Default::default();
506        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
507        syn::visit::visit_type(&mut visitor, &ty);
508
509        assert!(visitor.found_set.contains(&t_ident));
510        assert!(visitor.found_set.contains(&r_ident));
511    }
512
513    #[test]
514    fn test_generic_args_to_concrete() {
515        use std::borrow::Cow;
516        use std::collections::BTreeMap;
517
518        // T -> String replacement
519        let t = syn::parse_quote!(T);
520        let str: syn::Type = syn::parse_quote!(String);
521        let generic_names: BTreeMap<&syn::Ident, Option<Cow<syn::Type>>> = {
522            let mut map = BTreeMap::new();
523            map.insert(&t, Some(Cow::Borrowed(&str)));
524            map
525        };
526
527        // T gets replaced with String
528        let generic_type: syn::Type = syn::parse_quote!(Promise<T>);
529        let result =
530            crate::generics::generic_to_concrete(generic_type, &generic_names, &[]).unwrap();
531        let expected: syn::Type = syn::parse_quote!(Promise<String>);
532        assert_eq!(
533            quote::quote!(#result).to_string(),
534            quote::quote!(#expected).to_string()
535        );
536
537        // Mixed: i32 stays, T becomes String
538        let mixed_type: syn::Type = syn::parse_quote!(Promise<i32, T>);
539        let result = crate::generics::generic_to_concrete(mixed_type, &generic_names, &[]).unwrap();
540        let expected: syn::Type = syn::parse_quote!(Promise<i32, String>);
541        assert_eq!(
542            quote::quote!(#result).to_string(),
543            quote::quote!(#expected).to_string()
544        );
545
546        // No generics to replace - unchanged
547        let concrete_type: syn::Type = syn::parse_quote!(Promise<i32, bool>);
548        let result =
549            crate::generics::generic_to_concrete(concrete_type, &generic_names, &[]).unwrap();
550        let expected: syn::Type = syn::parse_quote!(Promise<i32, bool>);
551        assert_eq!(
552            quote::quote!(#result).to_string(),
553            quote::quote!(#expected).to_string()
554        );
555    }
556
557    #[test]
558    fn test_generic_associated_type_replacement() {
559        use std::borrow::Cow;
560        use std::collections::BTreeMap;
561
562        let t: syn::Ident = syn::parse_quote!(T);
563        let concrete: syn::Type = syn::parse_quote!(MyConcreteType);
564        let generic_names: BTreeMap<&syn::Ident, Option<Cow<syn::Type>>> = {
565            let mut map = BTreeMap::new();
566            map.insert(&t, Some(Cow::Borrowed(&concrete)));
567            map
568        };
569
570        // T::DurableObjectStub -> MyConcreteType::DurableObjectStub
571        let assoc_type: syn::Type = syn::parse_quote!(T::DurableObjectStub);
572        let result = crate::generics::generic_to_concrete(assoc_type, &generic_names, &[]).unwrap();
573        let expected: syn::Type = syn::parse_quote!(MyConcreteType::DurableObjectStub);
574        assert_eq!(
575            quote::quote!(#result).to_string(),
576            quote::quote!(#expected).to_string()
577        );
578
579        // Nested: Vec<T::Item> -> Vec<MyConcreteType::Item>
580        let nested: syn::Type = syn::parse_quote!(Vec<T::Item>);
581        let result = crate::generics::generic_to_concrete(nested, &generic_names, &[]).unwrap();
582        let expected: syn::Type = syn::parse_quote!(Vec<MyConcreteType::Item>);
583        assert_eq!(
584            quote::quote!(#result).to_string(),
585            quote::quote!(#expected).to_string()
586        );
587
588        // Complex: WasmRet<<T::Stub as FromWasmAbi>::Abi>
589        let complex: syn::Type = syn::parse_quote!(WasmRet<<T::Stub as FromWasmAbi>::Abi>);
590        let result = crate::generics::generic_to_concrete(complex, &generic_names, &[]).unwrap();
591        let expected: syn::Type =
592            syn::parse_quote!(WasmRet<<MyConcreteType::Stub as FromWasmAbi>::Abi>);
593        assert_eq!(
594            quote::quote!(#result).to_string(),
595            quote::quote!(#expected).to_string()
596        );
597
598        // T<Foo> gets fully replaced, args discarded
599        let with_args: syn::Type = syn::parse_quote!(T<SomeArg>);
600        let result = crate::generics::generic_to_concrete(with_args, &generic_names, &[]).unwrap();
601        let expected: syn::Type = syn::parse_quote!(MyConcreteType);
602        assert_eq!(
603            quote::quote!(#result).to_string(),
604            quote::quote!(#expected).to_string()
605        );
606
607        // QSelf: <T::DurableObjectStub as FromWasmAbi>::Abi
608        let qself_type: syn::Type = syn::parse_quote!(<T::DurableObjectStub as FromWasmAbi>::Abi);
609        let result = crate::generics::generic_to_concrete(qself_type, &generic_names, &[]).unwrap();
610        let expected: syn::Type =
611            syn::parse_quote!(<MyConcreteType::DurableObjectStub as FromWasmAbi>::Abi);
612        assert_eq!(
613            quote::quote!(#result).to_string(),
614            quote::quote!(#expected).to_string()
615        );
616
617        // QSelf with trait: <T as DurableObject>::DurableObjectStub
618        let qself_trait: syn::Type = syn::parse_quote!(<T as DurableObject>::DurableObjectStub);
619        let result =
620            crate::generics::generic_to_concrete(qself_trait, &generic_names, &[]).unwrap();
621        let expected: syn::Type =
622            syn::parse_quote!(<MyConcreteType as DurableObject>::DurableObjectStub);
623        assert_eq!(
624            quote::quote!(#result).to_string(),
625            quote::quote!(#expected).to_string()
626        );
627
628        // Reference to QSelf with trait: &<T as DurableObject>::DurableObjectStub
629        let ref_qself_trait: syn::Type =
630            syn::parse_quote!(&<T as DurableObject>::DurableObjectStub);
631        let result =
632            crate::generics::generic_to_concrete(ref_qself_trait, &generic_names, &[]).unwrap();
633        let expected: syn::Type =
634            syn::parse_quote!(&<MyConcreteType as DurableObject>::DurableObjectStub);
635        assert_eq!(
636            quote::quote!(#result).to_string(),
637            quote::quote!(#expected).to_string()
638        );
639    }
640
641    #[test]
642    fn test_where_predicate_assoc_type_binding() {
643        // Test that generics_predicate_uses finds generic params in associated type bindings
644        // This is the pattern: F: JsFunction<Ret = Ret>
645        // Both F and Ret should be detected as used
646
647        let f_ident = syn::Ident::new("F", proc_macro2::Span::call_site());
648        let ret_ident = syn::Ident::new("Ret", proc_macro2::Span::call_site());
649
650        // Test with both F and Ret in the search set
651        let generic_params = vec![&f_ident, &ret_ident];
652        let predicate: syn::WherePredicate = syn::parse_quote!(F: JsFunction<Ret = Ret>);
653
654        let mut found_set = Default::default();
655        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
656        syn::visit::visit_where_predicate(&mut visitor, &predicate);
657
658        assert!(
659            found_set.contains(&f_ident),
660            "F should be found in 'F: JsFunction<Ret = Ret>'"
661        );
662        assert!(
663            found_set.contains(&ret_ident),
664            "Ret should be found in 'F: JsFunction<Ret = Ret>' (RHS of assoc type binding)"
665        );
666    }
667
668    #[test]
669    fn test_where_predicate_assoc_type_binding_only_rhs() {
670        let f_ident = syn::Ident::new("F", proc_macro2::Span::call_site());
671        let ret_ident = syn::Ident::new("Ret", proc_macro2::Span::call_site());
672
673        // Ret in the search set
674        let generic_params = vec![&ret_ident];
675        let predicate: syn::WherePredicate = syn::parse_quote!(F: JsFunction<Ret = Ret>);
676
677        let uses = crate::generics::generics_predicate_uses(&predicate, &generic_params);
678        assert!(
679            uses,
680            "Ret should be detected as used in 'F: JsFunction<Ret = Ret>'"
681        );
682
683        // F in the search set
684        let not_generic_params = vec![&f_ident];
685        let uses = crate::generics::generics_predicate_uses(&predicate, &not_generic_params);
686        assert!(
687            uses,
688            "F should not be detected as used in 'F: JsFunction<Ret = Ret>'"
689        );
690    }
691
692    #[test]
693    fn test_where_predicate_assoc_type_binding_only_bounded() {
694        // Test that only F (not Ret) is found when Ret is not in the search set
695        let f_ident = syn::Ident::new("F", proc_macro2::Span::call_site());
696        let ret_ident = syn::Ident::new("Ret", proc_macro2::Span::call_site());
697
698        // Only F in the search set
699        let generic_params = vec![&f_ident];
700        let predicate: syn::WherePredicate = syn::parse_quote!(F: JsFunction<Ret = Ret>);
701
702        let uses = crate::generics::generics_predicate_uses(&predicate, &generic_params);
703        assert!(
704            uses,
705            "F should be detected as used in 'F: JsFunction<Ret = Ret>'"
706        );
707
708        // Also verify Ret is NOT found when not in the search set
709        let mut found_set = Default::default();
710        let mut visitor = crate::generics::GenericNameVisitor::new(&generic_params, &mut found_set);
711        syn::visit::visit_where_predicate(&mut visitor, &predicate);
712
713        assert!(found_set.contains(&f_ident), "F should be found");
714        assert!(
715            !found_set.contains(&ret_ident),
716            "Ret should NOT be found when not in search set"
717        );
718    }
719
720    #[test]
721    fn test_staticize_specific_lifetimes() {
722        // Test that specified lifetimes in types are replaced with 'static
723        let lifetime_a: syn::Lifetime = syn::parse_quote!('a);
724        let lifetimes = [&lifetime_a];
725
726        let ty: syn::Type = syn::parse_quote!(ImmediateClosure<'a, dyn FnMut(T) -> R>);
727        let result = crate::generics::staticize_lifetimes(ty, &lifetimes);
728        let expected: syn::Type = syn::parse_quote!(ImmediateClosure<'static, dyn FnMut(T) -> R>);
729        assert_eq!(
730            quote::quote!(#result).to_string(),
731            quote::quote!(#expected).to_string()
732        );
733
734        // Test multiple lifetimes - only staticize specified ones
735        let lifetime_b: syn::Lifetime = syn::parse_quote!('b);
736        let lifetimes_both = [&lifetime_a, &lifetime_b];
737        let ty: syn::Type = syn::parse_quote!(&'a SomeType<'b, T>);
738        let result = crate::generics::staticize_lifetimes(ty, &lifetimes_both);
739        let expected: syn::Type = syn::parse_quote!(&'static SomeType<'static, T>);
740        assert_eq!(
741            quote::quote!(#result).to_string(),
742            quote::quote!(#expected).to_string()
743        );
744
745        // Test selective staticization - only 'a, not 'b
746        let ty: syn::Type = syn::parse_quote!(&'a SomeType<'b, T>);
747        let result = crate::generics::staticize_lifetimes(ty, &[&lifetime_a]);
748        let expected: syn::Type = syn::parse_quote!(&'static SomeType<'b, T>);
749        assert_eq!(
750            quote::quote!(#result).to_string(),
751            quote::quote!(#expected).to_string()
752        );
753
754        // Test no lifetimes to staticize (should be unchanged)
755        let ty: syn::Type = syn::parse_quote!(Vec<T>);
756        let result = crate::generics::staticize_lifetimes(ty, &[]);
757        let expected: syn::Type = syn::parse_quote!(Vec<T>);
758        assert_eq!(
759            quote::quote!(#result).to_string(),
760            quote::quote!(#expected).to_string()
761        );
762    }
763
764    #[test]
765    fn test_generic_to_concrete_with_lifetimes() {
766        use std::borrow::Cow;
767        use std::collections::BTreeMap;
768
769        // Test that generic_to_concrete replaces both type params AND specified lifetimes
770        let t: syn::Ident = syn::parse_quote!(T);
771        let concrete: syn::Type = syn::parse_quote!(JsValue);
772        let generic_names: BTreeMap<&syn::Ident, Option<Cow<syn::Type>>> = {
773            let mut map = BTreeMap::new();
774            map.insert(&t, Some(Cow::Borrowed(&concrete)));
775            map
776        };
777
778        // Create the lifetime 'a that we want to staticize
779        let lifetime_a: syn::Lifetime = syn::parse_quote!('a);
780        let lifetimes_to_staticize = [&lifetime_a];
781
782        // ImmediateClosure<'a, dyn FnMut(T)> -> ImmediateClosure<'static, dyn FnMut(JsValue)>
783        let ty: syn::Type = syn::parse_quote!(ImmediateClosure<'a, dyn FnMut(T)>);
784        let result =
785            crate::generics::generic_to_concrete(ty, &generic_names, &lifetimes_to_staticize)
786                .unwrap();
787        let expected: syn::Type = syn::parse_quote!(ImmediateClosure<'static, dyn FnMut(JsValue)>);
788        assert_eq!(
789            quote::quote!(#result).to_string(),
790            quote::quote!(#expected).to_string()
791        );
792
793        // Test that lifetimes NOT in the list are preserved
794        let _lifetime_b: syn::Lifetime = syn::parse_quote!('b);
795        let lifetimes_only_a = [&lifetime_a];
796        let ty: syn::Type = syn::parse_quote!(Foo<'a, 'b>);
797        let result =
798            crate::generics::generic_to_concrete(ty, &BTreeMap::new(), &lifetimes_only_a).unwrap();
799        let expected: syn::Type = syn::parse_quote!(Foo<'static, 'b>);
800        assert_eq!(
801            quote::quote!(#result).to_string(),
802            quote::quote!(#expected).to_string()
803        );
804    }
805}