1
use syn::visit_mut::VisitMut;
2
use syn::{
3
    GenericArgument, Ident, NamedArg, PathArguments, ReturnType, Type, TypePath, parse_quote,
4
    punctuated::Punctuated,
5
};
6

            
7
280
pub fn type_is_ident(ty: &Type, ident: &Ident) -> bool {
8
280
    match ty {
9
280
        Type::Path(tp) => type_path_is_ident(tp, ident),
10
        _ => false,
11
    }
12
280
}
13

            
14
/// Check whether a type is `Option<ident>`.
15
90
pub fn type_is_option_of_ident(ty: &Type, ident: &Ident) -> bool {
16
90
    let Type::Path(path) = ty else {
17
        return false;
18
    };
19
90
    let Some(segment) = path.path.segments.last() else {
20
        return false;
21
    };
22
90
    if segment.ident != "Option" {
23
60
        return false;
24
30
    }
25
30
    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
26
        return false;
27
    };
28
30
    let [GenericArgument::Type(inner)] = arguments.args.iter().collect::<Vec<_>>().as_slice()
29
    else {
30
        return false;
31
    };
32
30
    type_is_ident(inner, ident)
33
90
}
34

            
35
/// Check whether a type path *is* exactly a given ident
36
2928
pub fn type_path_is_ident(tp: &TypePath, ident: &Ident) -> bool {
37
2928
    tp.qself.is_none()
38
2928
        && tp.path.segments.len() == 1
39
2928
        && tp.path.segments[0].ident == *ident
40
1097
        && tp.path.segments[0].arguments.is_empty()
41
2928
}
42

            
43
/// Check whether a type AST contains a reference to a specific ident
44
2431
pub fn type_contains_ident(ty: &Type, ident: &Ident) -> bool {
45
2431
    match ty {
46
        Type::Path(tp) => {
47
2255
            if type_path_is_ident(tp, ident) {
48
654
                return true;
49
1601
            }
50

            
51
            // Check type arguments recursively (e.g. `Vec<T>`, `Option<Vec<T>>`)
52
1601
            tp.path.segments.iter().any(|seg| {
53
1601
                if seg.ident == *ident {
54
                    return true;
55
1601
                }
56
1601
                match &seg.arguments {
57
                    syn::PathArguments::AngleBracketed(args) => {
58
802
                        args.args.iter().any(|arg| match arg {
59
                            syn::GenericArgument::Type(inner_ty) => {
60
802
                                type_contains_ident(inner_ty, ident)
61
                            }
62
                            _ => false,
63
802
                        })
64
                    }
65
                    syn::PathArguments::Parenthesized(args) => args
66
                        .inputs
67
                        .iter()
68
                        .any(|input| type_contains_ident(&input.ty, ident)),
69
832
                    syn::PathArguments::None => false,
70
                }
71
1601
            })
72
        }
73
352
        Type::Tuple(tt) => tt.elems.iter().any(|t| type_contains_ident(t, ident)),
74
        Type::Array(ta) => type_contains_ident(&ta.elem, ident),
75
        Type::Slice(ts) => type_contains_ident(&ts.elem, ident),
76
        Type::Reference(tr) => type_contains_ident(&tr.elem, ident),
77
        Type::Paren(tp) => type_contains_ident(&tp.elem, ident),
78
        _ => false,
79
    }
80
2431
}
81

            
82
/// Build a `serde_as` adapter type for `ty`, replacing occurrences of `ident` with
83
/// `replacement` and using `_` in adapter argument positions for non-target types.
84
253
pub fn build_serde_as_type(ty: &Type, ident: &Ident, replacement: &str) -> Type {
85
253
    let replacement_ident = Ident::new(replacement, ident.span());
86
253
    build_serde_as_type_inner(ty, ident, &replacement_ident, false)
87
253
}
88

            
89
417
fn build_serde_as_type_inner(
90
417
    ty: &Type,
91
417
    ident: &Ident,
92
417
    replacement: &Ident,
93
417
    in_adapter_position: bool,
94
417
) -> Type {
95
417
    if !type_contains_ident(ty, ident) {
96
13
        return if in_adapter_position {
97
13
            parse_quote!(_)
98
        } else {
99
            ty.clone()
100
        };
101
404
    }
102

            
103
404
    match ty {
104
393
        Type::Path(tp) if type_path_is_ident(tp, ident) => {
105
253
            parse_quote!(#replacement)
106
        }
107
        Type::Path(tp) => {
108
140
            let mut out = tp.clone();
109
140
            for seg in &mut out.path.segments {
110
140
                match &seg.arguments {
111
                    PathArguments::AngleBracketed(args) => {
112
140
                        let rewritten: Punctuated<GenericArgument, syn::token::Comma> = args
113
140
                            .args
114
140
                            .iter()
115
142
                            .map(|arg| rewrite_generic_arg(arg, ident, replacement))
116
                            .collect();
117
140
                        let mut new_args = args.clone();
118
140
                        new_args.args = rewritten;
119
140
                        seg.arguments = PathArguments::AngleBracketed(new_args);
120
                    }
121
                    PathArguments::Parenthesized(args) => {
122
                        let mut new_args = args.clone();
123
                        new_args.inputs = args
124
                            .inputs
125
                            .iter()
126
                            .map(|input| NamedArg {
127
                                attrs: input.attrs.clone(),
128
                                name: input.name.clone(),
129
                                ty: build_serde_as_type_inner(&input.ty, ident, replacement, true),
130
                            })
131
                            .collect();
132
                        new_args.output = match &args.output {
133
                            ReturnType::Default => ReturnType::Default,
134
                            ReturnType::Type(arrow, ty) => ReturnType::Type(
135
                                *arrow,
136
                                Box::new(build_serde_as_type_inner(ty, ident, replacement, true)),
137
                            ),
138
                        };
139
                        seg.arguments = PathArguments::Parenthesized(new_args);
140
                    }
141
                    PathArguments::None => {}
142
                }
143
            }
144
140
            Type::Path(out)
145
        }
146
        Type::Tuple(tuple) => {
147
11
            let mut out = tuple.clone();
148
11
            out.elems = tuple
149
11
                .elems
150
11
                .iter()
151
22
                .map(|elem| build_serde_as_type_inner(elem, ident, replacement, true))
152
                .collect();
153
11
            Type::Tuple(out)
154
        }
155
        Type::Array(array) => {
156
            let mut out = array.clone();
157
            out.elem = Box::new(build_serde_as_type_inner(
158
                &array.elem,
159
                ident,
160
                replacement,
161
                true,
162
            ));
163
            Type::Array(out)
164
        }
165
        Type::Slice(slice) => {
166
            let mut out = slice.clone();
167
            out.elem = Box::new(build_serde_as_type_inner(
168
                &slice.elem,
169
                ident,
170
                replacement,
171
                true,
172
            ));
173
            Type::Slice(out)
174
        }
175
        Type::Reference(reference) => {
176
            let mut out = reference.clone();
177
            out.elem = Box::new(build_serde_as_type_inner(
178
                &reference.elem,
179
                ident,
180
                replacement,
181
                in_adapter_position,
182
            ));
183
            Type::Reference(out)
184
        }
185
        Type::Paren(paren) => {
186
            let mut out = paren.clone();
187
            out.elem = Box::new(build_serde_as_type_inner(
188
                &paren.elem,
189
                ident,
190
                replacement,
191
                in_adapter_position,
192
            ));
193
            Type::Paren(out)
194
        }
195
        _ => {
196
            if in_adapter_position {
197
                parse_quote!(_)
198
            } else {
199
                ty.clone()
200
            }
201
        }
202
    }
203
417
}
204

            
205
142
fn rewrite_generic_arg(
206
142
    arg: &GenericArgument,
207
142
    ident: &Ident,
208
142
    replacement: &Ident,
209
142
) -> GenericArgument {
210
142
    match arg {
211
        GenericArgument::Type(ty) => {
212
142
            GenericArgument::Type(build_serde_as_type_inner(ty, ident, replacement, true))
213
        }
214
        GenericArgument::AssocType(assoc_ty) => {
215
            let mut out = assoc_ty.clone();
216
            out.ty = build_serde_as_type_inner(&assoc_ty.ty, ident, replacement, true);
217
            GenericArgument::AssocType(out)
218
        }
219
        _ => arg.clone(),
220
    }
221
142
}
222

            
223
/// Rename an `ItemFn`'s identifier, returning the modified function.
224
950
pub fn rename_fn(mut func: syn::ItemFn, new_name: &Ident) -> syn::ItemFn {
225
950
    func.sig.ident = new_name.clone();
226
950
    func
227
950
}
228

            
229
/// Rename all occurrences of `from` to `to` within an `ItemFn` (signature + body).
230
900
pub fn rename_ident_in_fn(mut func: syn::ItemFn, from: &Ident, to: &Ident) -> syn::ItemFn {
231
900
    let mut replacer = IdentReplacer {
232
900
        target: from.to_string(),
233
900
        replacement: to.clone(),
234
900
    };
235
900
    VisitMut::visit_item_fn_mut(&mut replacer, &mut func);
236
900
    func
237
900
}
238

            
239
/// Rename all occurrences of `from` to `to` within an `ItemImpl`
240
200
pub fn rename_ident_in_impl(mut item: syn::ItemImpl, from: &Ident, to: &Ident) -> syn::ItemImpl {
241
200
    let mut replacer = IdentReplacer {
242
200
        target: from.to_string(),
243
200
        replacement: to.clone(),
244
200
    };
245
200
    VisitMut::visit_item_impl_mut(&mut replacer, &mut item);
246
200
    item
247
200
}
248

            
249
/// A `VisitMut` that replaces all idents matching `target` with `replacement`.
250
struct IdentReplacer {
251
    target: String,
252
    replacement: Ident,
253
}
254

            
255
impl VisitMut for IdentReplacer {
256
67145
    fn visit_ident_mut(&mut self, i: &mut Ident) {
257
67145
        if *i == self.target {
258
1655
            *i = self.replacement.clone();
259
65490
        }
260
67145
    }
261
}
262

            
263
#[cfg(test)]
264
mod tests {
265
    use super::build_serde_as_type;
266
    use quote::ToTokens;
267
    use syn::{Ident, Type, parse_quote};
268

            
269
3
    fn assert_adapter_type(input: Type, expected: Type) {
270
3
        let generic = Ident::new("T", proc_macro2::Span::call_site());
271
3
        let actual = build_serde_as_type(&input, &generic, "ReprStateSerde");
272

            
273
3
        assert_eq!(
274
3
            actual.to_token_stream().to_string(),
275
3
            expected.to_token_stream().to_string()
276
        );
277
3
    }
278

            
279
    #[test]
280
1
    fn map_value_uses_repr_state_and_passthrough_key() {
281
1
        assert_adapter_type(
282
1
            parse_quote!(HashMap<Name, T>),
283
1
            parse_quote!(HashMap<_, ReprStateSerde>),
284
1
        );
285
1
    }
286

            
287
    #[test]
288
1
    fn nested_collections_are_rewritten_recursively() {
289
1
        assert_adapter_type(
290
1
            parse_quote!(Vec<HashMap<Name, Vec<T>>>),
291
1
            parse_quote!(Vec<HashMap<_, Vec<ReprStateSerde>>>),
292
1
        );
293
1
    }
294

            
295
    #[test]
296
1
    fn tuple_positions_use_passthrough_for_non_generic_elements() {
297
1
        assert_adapter_type(
298
1
            parse_quote!((Name, Option<T>)),
299
1
            parse_quote!((_, Option<ReprStateSerde>)),
300
1
        );
301
1
    }
302
}