use proc_macro2::Span;
use scale_info::form::PortableForm;
use std::{borrow::Borrow, collections::HashMap};
use syn::{parse_quote, spanned::Spanned as _, PathSegment};
use crate::{
typegen::{
error::{TypeSubstitutionError, TypeSubstitutionErrorKind},
type_path::{TypePath, TypePathType},
},
TypeGeneratorSettings,
};
use TypeSubstitutionErrorKind::*;
fn error(span: Span, kind: TypeSubstitutionErrorKind) -> TypeSubstitutionError {
TypeSubstitutionError { span, kind }
}
#[derive(Debug, Clone)]
pub struct TypeSubstitutes {
substitutes: HashMap<PathSegments, Substitute>,
}
pub type PathSegments = Vec<String>;
#[derive(Debug, Clone)]
pub struct Substitute {
path: syn::Path,
param_mapping: TypeParamMapping,
}
impl Substitute {
pub fn path(&self) -> &syn::Path {
&self.path
}
}
#[derive(Debug, Clone)]
enum TypeParamMapping {
PassThrough,
Specified(Vec<(syn::Ident, usize)>),
}
impl Default for TypeSubstitutes {
fn default() -> Self {
Self::new()
}
}
impl TypeSubstitutes {
pub fn new() -> Self {
Self {
substitutes: HashMap::new(),
}
}
pub fn insert(
&mut self,
source: syn::Path,
target: AbsolutePath,
) -> Result<(), TypeSubstitutionError> {
let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
self.substitutes.insert(key, val);
Ok(())
}
pub fn insert_if_not_exists(
&mut self,
source: syn::Path,
target: AbsolutePath,
) -> Result<(), TypeSubstitutionError> {
let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
self.substitutes.entry(key).or_insert(val);
Ok(())
}
pub fn extend(
&mut self,
elems: impl IntoIterator<Item = (syn::Path, AbsolutePath)>,
) -> Result<(), TypeSubstitutionError> {
for (source, target) in elems.into_iter() {
let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
self.substitutes.insert(key, val);
}
Ok(())
}
fn parse_path_substitution(
src_path: syn::Path,
target_path: syn::Path,
) -> Result<(PathSegments, Substitute), TypeSubstitutionError> {
let param_mapping = Self::parse_path_param_mapping(&src_path, &target_path)?;
let src_path = path_segments(&src_path);
Ok((
src_path,
Substitute {
path: target_path,
param_mapping,
},
))
}
fn parse_path_param_mapping(
src_path: &syn::Path,
target_path: &syn::Path,
) -> Result<TypeParamMapping, TypeSubstitutionError> {
let Some(syn::PathSegment {
arguments: src_path_args,
..
}) = src_path.segments.last()
else {
return Err(error(src_path.span(), EmptySubstitutePath));
};
let Some(syn::PathSegment {
arguments: target_path_args,
..
}) = target_path.segments.last()
else {
return Err(error(target_path.span(), EmptySubstitutePath));
};
let source_args = match src_path_args {
syn::PathArguments::None => {
Vec::new()
}
syn::PathArguments::AngleBracketed(args) => {
args.args
.iter()
.map(|arg| match get_valid_from_substitution_type(arg) {
Some(ident) => Ok(ident),
None => Err(error(arg.span(), InvalidFromType)),
})
.collect::<Result<Vec<_>, _>>()?
}
syn::PathArguments::Parenthesized(args) => {
return Err(error(args.span(), ExpectedAngleBracketGenerics));
}
};
let target_args = match target_path_args {
syn::PathArguments::None => {
Vec::new()
}
syn::PathArguments::AngleBracketed(args) => {
args.args
.iter()
.map(|arg| match get_valid_to_substitution_type(arg) {
Some(arg) => Ok(arg),
None => Err(error(arg.span(), InvalidToType)),
})
.collect::<Result<Vec<_>, _>>()?
}
syn::PathArguments::Parenthesized(args) => {
return Err(error(args.span(), ExpectedAngleBracketGenerics));
}
};
if source_args.is_empty() && target_args.is_empty() {
return Ok(TypeParamMapping::PassThrough);
}
let mapping = source_args
.into_iter()
.enumerate()
.map(|(idx, ident)| (ident.clone(), idx))
.collect();
Ok(TypeParamMapping::Specified(mapping))
}
pub fn contains(&self, path: &PathSegments) -> bool {
!path.is_empty() && self.substitutes.contains_key(path)
}
pub fn for_path_with_params(
&self,
path: &PathSegments,
params: &[TypePath],
settings: &TypeGeneratorSettings,
) -> Option<TypePathType> {
fn replace_params(
mut substitute_path: syn::Path,
params: &[TypePath],
mapping: &TypeParamMapping,
settings: &TypeGeneratorSettings,
) -> TypePathType {
match mapping {
TypeParamMapping::Specified(mapping) => {
let replacement_map: Vec<(&syn::Ident, &TypePath)> = mapping
.iter()
.filter_map(|(ident, idx)| params.get(*idx).map(|param| (ident, param)))
.collect();
if !replacement_map.is_empty() {
replace_path_params_recursively(
&mut substitute_path,
&replacement_map,
settings,
);
}
TypePathType::Path {
path: substitute_path,
params: Vec::new(),
}
}
TypeParamMapping::PassThrough => TypePathType::Path {
path: substitute_path,
params: params.to_vec(),
},
}
}
self.substitutes
.get(path)
.map(|sub| replace_params(sub.path.clone(), params, &sub.param_mapping, settings))
}
pub fn iter(&self) -> impl Iterator<Item = (&PathSegments, &Substitute)> {
self.substitutes.iter()
}
}
fn replace_path_params_recursively<I: Borrow<syn::Ident>, P: Borrow<TypePath>>(
path: &mut syn::Path,
params: &Vec<(I, P)>,
settings: &TypeGeneratorSettings,
) {
for segment in &mut path.segments {
let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments else {
continue;
};
for arg in &mut args.args {
let syn::GenericArgument::Type(ty) = arg else {
continue;
};
let syn::Type::Path(path) = ty else {
continue;
};
if let Some(ident) = get_ident_from_type_path(path) {
if let Some((_, replacement)) = params.iter().find(|(i, _)| ident == i.borrow()) {
*ty = replacement.borrow().to_syn_type(&settings.alloc_crate_path);
continue;
}
}
replace_path_params_recursively(&mut path.path, params, settings);
}
}
}
fn get_valid_to_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::TypePath> {
let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
return None;
};
Some(type_path)
}
fn get_valid_from_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::Ident> {
let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
return None;
};
get_ident_from_type_path(type_path)
}
fn get_ident_from_type_path(type_path: &syn::TypePath) -> Option<&syn::Ident> {
if type_path.qself.is_some() {
return None;
}
if type_path.path.leading_colon.is_some() {
return None;
}
if type_path.path.segments.len() > 1 {
return None;
}
let Some(segment) = type_path.path.segments.last() else {
return None;
};
if !segment.arguments.is_empty() {
return None;
}
Some(&segment.ident)
}
fn is_absolute(path: &syn::Path) -> bool {
path.leading_colon.is_some()
|| path
.segments
.first()
.is_some_and(|segment| segment.ident == "crate")
}
pub fn absolute_path(path: syn::Path) -> Result<AbsolutePath, TypeSubstitutionError> {
path.try_into()
}
pub fn path_segments(path: &syn::Path) -> PathSegments {
path.segments.iter().map(|x| x.ident.to_string()).collect()
}
pub trait TryIntoSynPath {
fn syn_path(self) -> Option<syn::Path>;
}
impl TryIntoSynPath for &scale_info::Path<PortableForm> {
fn syn_path(self) -> Option<syn::Path> {
if self.segments.is_empty() {
return None;
}
let segments = self.segments.iter().map(|e| {
syn::parse_str::<PathSegment>(e)
.expect("scale_info::Path segments should be syn::PathSegment compatible")
});
Some(parse_quote!(#(#segments)::*))
}
}
impl TryIntoSynPath for syn::Path {
fn syn_path(self) -> Option<syn::Path> {
Some(self)
}
}
pub struct AbsolutePath(syn::Path);
impl TryFrom<syn::Path> for AbsolutePath {
type Error = TypeSubstitutionError;
fn try_from(value: syn::Path) -> Result<Self, Self::Error> {
if is_absolute(&value) {
Ok(AbsolutePath(value))
} else {
Err(error(value.span(), ExpectedAbsolutePath))
}
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! syn_path {
($path:path) => {{
let path: syn::Path = syn::parse_quote!($path);
path
}};
}
macro_rules! type_path {
($path:path) => {{
let path: syn::Path = syn::parse_quote!($path);
TypePath::from_syn_path(path)
}};
}
fn ident(name: &'static str) -> syn::Ident {
syn::Ident::new(name, proc_macro2::Span::call_site())
}
#[test]
#[rustfmt::skip]
fn replacing_nested_type_params_works() {
let paths = [
(
syn_path!(::some::path::Foo<::other::Path<A, B>>),
vec![],
syn_path!(::some::path::Foo<::other::Path<A, B>>),
),
(
syn_path!(::some::path::Foo<A>),
vec![(ident("A"), type_path!(::new::Value))],
syn_path!(::some::path::Foo<::new::Value>),
),
(
syn_path!(::some::path::Foo<::other::Path<A, B>>),
vec![(ident("A"), type_path!(::new::Value))],
syn_path!(::some::path::Foo<::other::Path<::new::Value, B>>),
),
(
syn_path!(::some::path::Foo<::other::Path<A, B>>),
vec![
(ident("A"), type_path!(::new::A)),
(ident("B"), type_path!(::new::B)),
],
syn_path!(::some::path::Foo<::other::Path<::new::A, ::new::B>>),
),
(
syn_path!(::some::path::Foo<::other::Path<A, ::more::path::to<::something::Argh<B>>>, C>),
vec![
(ident("A"), type_path!(::new::A)),
(ident("B"), type_path!(::new::B)),
],
syn_path!(::some::path::Foo<::other::Path<::new::A, ::more::path::to<::something::Argh<::new::B>>>,C>),
),
(
syn_path!(::some::path::Foo<::other::Path<A, ::foo::Argh<A, B>, A>>),
vec![(ident("A"), type_path!(::new::Value))],
syn_path!(::some::path::Foo<::other::Path<::new::Value, ::foo::Argh<::new::Value, B>, ::new::Value>>),
),
];
let settings = TypeGeneratorSettings::new();
for (mut path, replacements, expected) in paths {
replace_path_params_recursively(&mut path, &replacements, &settings);
assert_eq!(path, expected);
}
}
}