scale_typegen/typegen/settings/
substitutes.rs

1use proc_macro2::Span;
2use scale_info::form::PortableForm;
3use std::{borrow::Borrow, collections::HashMap};
4use syn::{parse_quote, spanned::Spanned as _, PathSegment};
5
6use crate::{
7    typegen::{
8        error::{TypeSubstitutionError, TypeSubstitutionErrorKind},
9        type_path::{TypePath, TypePathType},
10    },
11    TypeGeneratorSettings,
12};
13
14use TypeSubstitutionErrorKind::*;
15
16fn error(span: Span, kind: TypeSubstitutionErrorKind) -> TypeSubstitutionError {
17    TypeSubstitutionError { span, kind }
18}
19
20/// A map of type substitutes. We match on the paths to generated types in order
21/// to figure out when to swap said type with some provided substitute.
22#[derive(Debug, Clone)]
23pub struct TypeSubstitutes {
24    substitutes: HashMap<PathSegments, Substitute>,
25}
26
27/// We use this `Vec<String>` as a common denominator, since we need a consistent key for both
28/// `syn::TypePath` and `scale_info::ty::path::Path` types.
29///
30/// Can be obtained from a [`syn::Path`] via the [`path_segments`] function.
31pub type PathSegments = Vec<String>;
32
33/// A type that substitutes another type.
34#[derive(Debug, Clone)]
35pub struct Substitute {
36    path: syn::Path,
37    param_mapping: TypeParamMapping,
38}
39
40impl Substitute {
41    /// Returns the type path of the substitute.
42    pub fn path(&self) -> &syn::Path {
43        &self.path
44    }
45}
46
47#[derive(Debug, Clone)]
48enum TypeParamMapping {
49    // Pass any generics from source to target type
50    PassThrough,
51    // Replace any ident seen in the path with the input generic type at this index
52    Specified(Vec<(syn::Ident, usize)>),
53}
54
55impl Default for TypeSubstitutes {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl TypeSubstitutes {
62    /// Creates a new `TypeSubstitutes` with no default derives.
63    pub fn new() -> Self {
64        Self {
65            substitutes: HashMap::new(),
66        }
67    }
68
69    /// Insert the given substitution, overwriting any other with the same path.
70    pub fn insert(
71        &mut self,
72        source: syn::Path,
73        target: AbsolutePath,
74    ) -> Result<(), TypeSubstitutionError> {
75        let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
76        self.substitutes.insert(key, val);
77        Ok(())
78    }
79
80    /// Only insert the given substitution if a substitution at that path doesn't
81    /// already exist.
82    pub fn insert_if_not_exists(
83        &mut self,
84        source: syn::Path,
85        target: AbsolutePath,
86    ) -> Result<(), TypeSubstitutionError> {
87        let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
88        self.substitutes.entry(key).or_insert(val);
89        Ok(())
90    }
91
92    /// Add a bunch of source to target type substitutions.
93    pub fn extend(
94        &mut self,
95        elems: impl IntoIterator<Item = (syn::Path, AbsolutePath)>,
96    ) -> Result<(), TypeSubstitutionError> {
97        for (source, target) in elems.into_iter() {
98            let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
99            self.substitutes.insert(key, val);
100        }
101        Ok(())
102    }
103
104    /// Given a source and target path, parse the type params to work out the mapping from
105    /// source to target, and output the source => substitution mapping that we work out from this.
106    fn parse_path_substitution(
107        src_path: syn::Path,
108        target_path: syn::Path,
109    ) -> Result<(PathSegments, Substitute), TypeSubstitutionError> {
110        let param_mapping = Self::parse_path_param_mapping(&src_path, &target_path)?;
111        let src_path = path_segments(&src_path);
112        Ok((
113            src_path,
114            Substitute {
115                // Note; at this point, target_path might have some generics still. These
116                // might be hardcoded types that we want to keep, so leave them here for now.
117                path: target_path,
118                param_mapping,
119            },
120        ))
121    }
122
123    /// Given a source and target path, parse the type params to work out the mapping from
124    /// source to target, and return it.
125    fn parse_path_param_mapping(
126        src_path: &syn::Path,
127        target_path: &syn::Path,
128    ) -> Result<TypeParamMapping, TypeSubstitutionError> {
129        let Some(syn::PathSegment {
130            arguments: src_path_args,
131            ..
132        }) = src_path.segments.last()
133        else {
134            return Err(error(src_path.span(), EmptySubstitutePath));
135        };
136        let Some(syn::PathSegment {
137            arguments: target_path_args,
138            ..
139        }) = target_path.segments.last()
140        else {
141            return Err(error(target_path.span(), EmptySubstitutePath));
142        };
143
144        // Get hold of the generic args for the "from" type, erroring if they aren't valid.
145        let source_args = match src_path_args {
146            syn::PathArguments::None => {
147                // No generics defined on the source type:
148                Vec::new()
149            }
150            syn::PathArguments::AngleBracketed(args) => {
151                // We have generics like <A,B> defined on the source type (error for any non-ident type):
152                args.args
153                    .iter()
154                    .map(|arg| match get_valid_from_substitution_type(arg) {
155                        Some(ident) => Ok(ident),
156                        None => Err(error(arg.span(), InvalidFromType)),
157                    })
158                    .collect::<Result<Vec<_>, _>>()?
159            }
160            syn::PathArguments::Parenthesized(args) => {
161                // Generics like (A,B) -> defined; not allowed:
162                return Err(error(args.span(), ExpectedAngleBracketGenerics));
163            }
164        };
165
166        // Get hold of the generic args for the "to" type, erroring if they aren't valid.
167        let target_args = match target_path_args {
168            syn::PathArguments::None => {
169                // No generics on target.
170                Vec::new()
171            }
172            syn::PathArguments::AngleBracketed(args) => {
173                // We have generics like <A,B> defined on the target type.
174                args.args
175                    .iter()
176                    .map(|arg| match get_valid_to_substitution_type(arg) {
177                        Some(arg) => Ok(arg),
178                        None => Err(error(arg.span(), InvalidToType)),
179                    })
180                    .collect::<Result<Vec<_>, _>>()?
181            }
182            syn::PathArguments::Parenthesized(args) => {
183                // Generics like (A,B) -> defined; not allowed:
184                return Err(error(args.span(), ExpectedAngleBracketGenerics));
185            }
186        };
187
188        // If no generics defined on source or target, we just apply any concrete generics
189        // to the substitute type.
190        if source_args.is_empty() && target_args.is_empty() {
191            return Ok(TypeParamMapping::PassThrough);
192        }
193
194        // Make a note of the idents in the source args and their indexes.
195        let mapping = source_args
196            .into_iter()
197            .enumerate()
198            .map(|(idx, ident)| (ident.clone(), idx))
199            .collect();
200
201        Ok(TypeParamMapping::Specified(mapping))
202    }
203
204    /// Given a source type path, return whether a substitute exists for it.
205    pub fn contains(&self, path: &PathSegments) -> bool {
206        !path.is_empty() && self.substitutes.contains_key(path)
207    }
208
209    /// Given a source type path and the resolved, supplied type parameters,
210    /// return a new path and optionally overwritten type parameters.
211    pub fn for_path_with_params(
212        &self,
213        path: &PathSegments,
214        params: &[TypePath],
215        settings: &TypeGeneratorSettings,
216    ) -> Option<TypePathType> {
217        // If we find a substitute type, we'll take the substitute path, and
218        // swap any idents with their new concrete types.
219        fn replace_params(
220            mut substitute_path: syn::Path,
221            params: &[TypePath],
222            mapping: &TypeParamMapping,
223            settings: &TypeGeneratorSettings,
224        ) -> TypePathType {
225            match mapping {
226                // We need to map the input params to the output params somehow:
227                TypeParamMapping::Specified(mapping) => {
228                    // A map from ident name to replacement path.
229                    let replacement_map: Vec<(&syn::Ident, &TypePath)> = mapping
230                        .iter()
231                        .filter_map(|(ident, idx)| params.get(*idx).map(|param| (ident, param)))
232                        .collect();
233
234                    // Replace params in our substitute path with the incoming ones as needed.
235                    // No need if no replacements given.
236                    if !replacement_map.is_empty() {
237                        replace_path_params_recursively(
238                            &mut substitute_path,
239                            &replacement_map,
240                            settings,
241                        );
242                    }
243
244                    TypePathType::Path {
245                        path: substitute_path,
246                        params: Vec::new(),
247                    }
248                }
249                // No mapping; just hand back the substitute path and input params.
250                TypeParamMapping::PassThrough => TypePathType::Path {
251                    path: substitute_path,
252                    params: params.to_vec(),
253                },
254            }
255        }
256
257        self.substitutes
258            .get(path)
259            .map(|sub| replace_params(sub.path.clone(), params, &sub.param_mapping, settings))
260    }
261
262    /// Returns an iterator over all substitutes.
263    pub fn iter(&self) -> impl Iterator<Item = (&PathSegments, &Substitute)> {
264        self.substitutes.iter()
265    }
266}
267
268/// Dig through a `syn::TypePath` (this is provided by the user in a type substitution definition as the "to" type) and
269/// swap out any type params which match the idents given in the "from" type with the corresponding concrete types.
270///
271/// eg if we have:
272///
273/// ```text
274/// from = sp_runtime::MultiAddress<A, B>,
275/// to = ::subxt::utils::Static<::sp_runtime::MultiAddress<A, B>>
276/// ```
277///
278/// And we encounter a `sp_runtime::MultiAddress<Foo, Bar>`, then we will pass the `::sp_runtime::MultiAddress<A, B>`
279/// type param value into this call to turn it into `::sp_runtime::MultiAddress<Foo, Bar>`.
280fn replace_path_params_recursively<I: Borrow<syn::Ident>, P: Borrow<TypePath>>(
281    path: &mut syn::Path,
282    params: &Vec<(I, P)>,
283    settings: &TypeGeneratorSettings,
284) {
285    for segment in &mut path.segments {
286        let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments else {
287            continue;
288        };
289        for arg in &mut args.args {
290            let syn::GenericArgument::Type(ty) = arg else {
291                continue;
292            };
293            let syn::Type::Path(path) = ty else {
294                continue;
295            };
296            if let Some(ident) = get_ident_from_type_path(path) {
297                if let Some((_, replacement)) = params.iter().find(|(i, _)| ident == i.borrow()) {
298                    *ty = replacement.borrow().to_syn_type(&settings.alloc_crate_path);
299                    continue;
300                }
301            }
302            replace_path_params_recursively(&mut path.path, params, settings);
303        }
304    }
305}
306
307/// Given a "to" type in a type substitution, return the TypePath inside or None if
308/// it's not a valid "to" type.
309fn get_valid_to_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::TypePath> {
310    let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
311        // We are looking for a type, not a lifetime or anything else
312        return None;
313    };
314    Some(type_path)
315}
316
317/// Given a "from" type in a type substitution, return the Ident inside or None if
318/// it's not a valid "from" type.
319fn get_valid_from_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::Ident> {
320    let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
321        // We are looking for a type, not a lifetime or anything else
322        return None;
323    };
324    get_ident_from_type_path(type_path)
325}
326
327/// Given a type path, return the single ident representing it if that's all it is.
328fn get_ident_from_type_path(type_path: &syn::TypePath) -> Option<&syn::Ident> {
329    if type_path.qself.is_some() {
330        // No "<Foo as Bar>" type thing
331        return None;
332    }
333    if type_path.path.leading_colon.is_some() {
334        // No leading "::"
335        return None;
336    }
337    if type_path.path.segments.len() > 1 {
338        // The path should just be a single ident, not multiple
339        return None;
340    }
341    let Some(segment) = type_path.path.segments.last() else {
342        // Get the single ident (should be infallible)
343        return None;
344    };
345    if !segment.arguments.is_empty() {
346        // The ident shouldn't have any of it's own generic args like A<B, C>
347        return None;
348    }
349    Some(&segment.ident)
350}
351
352/// Whether a path is absolute - starts with `::` or `crate`.
353fn is_absolute(path: &syn::Path) -> bool {
354    path.leading_colon.is_some()
355        || path
356            .segments
357            .first()
358            .is_some_and(|segment| segment.ident == "crate")
359}
360
361/// tries to convert a [`syn::Path`] into an `AbsolutePath`. Only succeeds if the path is not a relative path.
362pub fn absolute_path(path: syn::Path) -> Result<AbsolutePath, TypeSubstitutionError> {
363    path.try_into()
364}
365
366/// Converts a [`syn::Path`] into a `Vec<String>` that contains all the [`syn::Ident`]s of the path as strings.
367pub fn path_segments(path: &syn::Path) -> PathSegments {
368    path.segments.iter().map(|x| x.ident.to_string()).collect()
369}
370
371/// New-type trait for `TryInto<syn::Path>`
372pub trait TryIntoSynPath {
373    /// Turns the type into a [`syn::Path`]. Returns None for empty paths.
374    fn syn_path(self) -> Option<syn::Path>;
375}
376
377impl TryIntoSynPath for &scale_info::Path<PortableForm> {
378    fn syn_path(self) -> Option<syn::Path> {
379        if self.segments.is_empty() {
380            return None;
381        }
382        let segments = self.segments.iter().map(|e| {
383            syn::parse_str::<PathSegment>(e)
384                .expect("scale_info::Path segments should be syn::PathSegment compatible")
385        });
386        Some(parse_quote!(#(#segments)::*))
387    }
388}
389
390impl TryIntoSynPath for syn::Path {
391    fn syn_path(self) -> Option<syn::Path> {
392        Some(self)
393    }
394}
395
396/// New-type wrapper around [`syn::Path`]
397pub struct AbsolutePath(syn::Path);
398
399impl TryFrom<syn::Path> for AbsolutePath {
400    type Error = TypeSubstitutionError;
401    fn try_from(value: syn::Path) -> Result<Self, Self::Error> {
402        if is_absolute(&value) {
403            Ok(AbsolutePath(value))
404        } else {
405            Err(error(value.span(), ExpectedAbsolutePath))
406        }
407    }
408}
409
410#[cfg(test)]
411mod test {
412    use super::*;
413
414    macro_rules! syn_path {
415        ($path:path) => {{
416            let path: syn::Path = syn::parse_quote!($path);
417            path
418        }};
419    }
420
421    macro_rules! type_path {
422        ($path:path) => {{
423            let path: syn::Path = syn::parse_quote!($path);
424            TypePath::from_syn_path(path)
425        }};
426    }
427
428    fn ident(name: &'static str) -> syn::Ident {
429        syn::Ident::new(name, proc_macro2::Span::call_site())
430    }
431
432    #[test]
433    #[rustfmt::skip]
434    fn replacing_nested_type_params_works() {
435        // Original path, replacement ident->paths, expected output path
436        let paths = [
437            // Works ok if nothing to replace
438            (
439                syn_path!(::some::path::Foo<::other::Path<A, B>>),
440                vec![],
441                syn_path!(::some::path::Foo<::other::Path<A, B>>),
442            ),
443            // Simple top level replacing
444            (
445                syn_path!(::some::path::Foo<A>),
446                vec![(ident("A"), type_path!(::new::Value))],
447                syn_path!(::some::path::Foo<::new::Value>),
448            ),
449            // More deeply nested replacing works too
450            (
451                syn_path!(::some::path::Foo<::other::Path<A, B>>),
452                vec![(ident("A"), type_path!(::new::Value))],
453                syn_path!(::some::path::Foo<::other::Path<::new::Value, B>>),
454            ),
455            (
456                syn_path!(::some::path::Foo<::other::Path<A, B>>),
457                vec![
458                    (ident("A"), type_path!(::new::A)),
459                    (ident("B"), type_path!(::new::B)),
460                ],
461                syn_path!(::some::path::Foo<::other::Path<::new::A, ::new::B>>),
462            ),
463            (
464                syn_path!(::some::path::Foo<::other::Path<A, ::more::path::to<::something::Argh<B>>>, C>),
465                vec![
466                    (ident("A"), type_path!(::new::A)),
467                    (ident("B"), type_path!(::new::B)),
468                ],
469                syn_path!(::some::path::Foo<::other::Path<::new::A, ::more::path::to<::something::Argh<::new::B>>>,C>),
470            ),
471            // The same ident will be replaced as many times as needed:
472            (
473                syn_path!(::some::path::Foo<::other::Path<A, ::foo::Argh<A, B>, A>>),
474                vec![(ident("A"), type_path!(::new::Value))],
475                syn_path!(::some::path::Foo<::other::Path<::new::Value, ::foo::Argh<::new::Value, B>, ::new::Value>>),
476            ),
477        ];
478
479        let settings = TypeGeneratorSettings::new();
480        for (mut path, replacements, expected) in paths {
481            replace_path_params_recursively(&mut path, &replacements, &settings);
482            assert_eq!(path, expected);
483        }
484    }
485}