Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Xanewok committed Dec 5, 2022
1 parent a0b92ae commit 1a049b0
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 47 deletions.
57 changes: 18 additions & 39 deletions codegen/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,19 @@ mod type_def_params;
mod type_path;

use darling::FromMeta;
use proc_macro2::{
Ident,
Span,
TokenStream,
};
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_error::abort_call_site;
use quote::{
quote,
ToTokens,
};
use scale_info::{
form::PortableForm,
PortableRegistry,
Type,
TypeDef,
};
use quote::{quote, ToTokens};
use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef};
use std::collections::BTreeMap;

pub use self::{
composite_def::{
CompositeDef,
CompositeDefFieldType,
CompositeDefFields,
},
derives::{
Derives,
DerivesRegistry,
},
composite_def::{CompositeDef, CompositeDefFieldType, CompositeDefFields},
derives::{Derives, DerivesRegistry},
substitutes::TypeSubstitutes,
type_def::TypeDefGen,
type_def_params::TypeDefParameters,
type_path::{
TypeParameter,
TypePath,
TypePathType,
},
type_path::{TypeParameter, TypePath, TypePathType},
};

pub type Field = scale_info::Field<PortableForm>;
Expand Down Expand Up @@ -97,11 +74,14 @@ impl<'a> TypeGenerator<'a> {
// not be in the type registry + our resolution already performs the substitution.
let joined_path = path.segments().join("::");
if self.type_substitutes.inner.contains_key(&joined_path) {
continue
continue;
}

// Lazily create submodules for the encountered namespace path, if they don't exist
let namespace = path.namespace();
// prelude types e.g. Option/Result have no namespace, so we don't generate them
if namespace.is_empty() { continue; }

// Lazily create submodules for the encountered namespace path, if they don't exist
let innermost_module = namespace
.iter()
.map(|segment| Ident::new(segment, Span::call_site()))
Expand All @@ -112,13 +92,10 @@ impl<'a> TypeGenerator<'a> {
.or_insert_with(|| Module::new(ident, root_mod_ident.clone()))
});

// prelude types e.g. Option/Result have no namespace, so we don't generate them
if !namespace.is_empty() {
innermost_module.types.insert(
path.clone(),
TypeDefGen::from_type(ty.ty(), self, &self.crate_path),
);
}
}

root_mod
Expand Down Expand Up @@ -194,7 +171,7 @@ impl<'a> TypeGenerator<'a> {
)
}

let params = ty
let params: Vec<_> = ty
.type_params()
.iter()
.filter_map(|f| {
Expand All @@ -207,12 +184,14 @@ impl<'a> TypeGenerator<'a> {
let ty = match ty.type_def() {
TypeDef::Composite(_) | TypeDef::Variant(_) => {
let joined_path = ty.path().segments().join("::");
if let Some(substitute_type_path) =
self.type_substitutes.inner.get(&joined_path)
let syn_path = syn::parse_str(&joined_path).expect("path to be valid");
if let Some((path, params)) = self
.type_substitutes
.for_path_with_params(&syn_path, &params)
{
TypePathType::Path {
path: substitute_type_path.clone(),
params,
path: path.clone(),
params: params.to_vec(),
}
} else {
TypePathType::from_type_def_path(
Expand Down
97 changes: 95 additions & 2 deletions codegen/src/types/substitutes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

use crate::CratePath;
use darling::ToTokens as _;
use proc_macro_error::{abort, emit_warning};
use std::collections::HashMap;
use syn::parse_quote;
use syn::{parse_quote, spanned::Spanned as _};

use super::{TypePath, TypePathType};

#[derive(Debug)]
pub struct TypeSubstitutes {
pub(crate) inner: HashMap<String, syn::TypePath>,
params: HashMap<String, Vec<TypePath>>,
}

impl TypeSubstitutes {
Expand Down Expand Up @@ -61,6 +65,7 @@ impl TypeSubstitutes {
.into_iter()
.map(|(path, substitute)| (path.to_owned(), substitute))
.collect(),
params: Default::default(),
}
}

Expand All @@ -70,13 +75,101 @@ impl TypeSubstitutes {
) {
self.inner
.extend(elems.into_iter().map(|(ty, AbsoluteTypePath(with))| {
// TODO: Verify both paths
let src_namespace = || ty.path.segments.iter().rev().skip(1);
if let Some(seg) = src_namespace()
.find(|seg| !seg.arguments.is_none() && !seg.arguments.is_empty())
{
abort!(seg.arguments.span(), "Namespace segment can't be generic");
}
let Some(syn::PathSegment { arguments: src_path_args, ..}) = ty.path.segments.last() else { abort!(ty.span(), "Empty path") };
let Some(syn::PathSegment { arguments: target_path_args, ..}) = with.path.segments.last() else { abort!(ty.span(), "Empty path") };

let source_args: Vec<_> = type_args(src_path_args).collect();
// Generics were specified in the source type, so we substitute
// them
if !source_args.is_empty() {
let new_params = type_args(target_path_args).map(|arg| {
// TODO: Handle nested generics in a substituted path
if let Some(&src) = source_args.iter().find(|&src| src == &arg) {
// TODO: This surely wrongly interacts with unused generics etc.
TypePath::Type(TypePathType::Path { path: src.clone(), params: Vec::new()})
}
else if is_absolute(arg) {
TypePath::Type(TypePathType::Path { path: arg.clone(), params: Vec::new()})
} else {
abort!(arg.span(), "Generic parameter {} couldn't be found or not absolute")
}
}).collect();

self.params.insert(ty.to_token_stream().to_string().replace(' ', ""), new_params);
}

(
// TODO(xanewok): Take a special care around generics, qualified path etc.
ty.into_token_stream().to_string().replace(' ', ""),
ty.to_token_stream().to_string().replace(' ', ""),
with,
)
}));
}

/// Given a source type path and the (already resolved? this can't be right)
/// type parameters, return a new path and optionally overwritten type parameters
pub fn for_path_with_params<'a: 'b, 'b>(
&'a self,
path: &syn::TypePath,
params: &'b [TypePath],
) -> Option<(&'a syn::TypePath, &'b [TypePath])> {
// We only support:
// 1. Reordering the generics
// 2. Replacing the generic type with a concrete type (won't this affect parent_type_params logic?)
// TypePath::Type(TypePathType::Path { path: todo!(), params: Vec::new()})
// 3. Omitting certain generics

let path_key = path.to_token_stream().to_string().replace(' ', "");
if let Some(sub) = self.inner.get(&path_key) {
let params = self
.params
.get(&path_key)
.map(Vec::as_slice)
.unwrap_or(params);

return Some((sub, params));
} else {
return None;
}
}
}

/// Returns an iterator over generic type parameters for `syn::PathArguments`.
/// For example:
/// - `<'a, T>` should only return T
/// - `(A, B) -> String` shouldn't return anything
fn type_args(path_args: &syn::PathArguments) -> impl Iterator<Item = &syn::TypePath> {
let args_opt = match path_args {
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
ref args,
..
}) => Some(args),
_ => None,
};

args_opt
.into_iter()
.flat_map(|x| x)
.filter_map(|arg| match arg {
syn::GenericArgument::Type(syn::Type::Path(path)) => Some(path),
_ => None,
})
}

fn is_absolute(value: &syn::TypePath) -> bool {
value.path.leading_colon.is_some()
|| value
.path
.segments
.first()
.map_or(false, |segment| segment.ident == "crate")
}

pub struct AbsoluteTypePath(syn::TypePath);
Expand Down
14 changes: 8 additions & 6 deletions examples/examples/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
//! ```
use sp_keyring::AccountKeyring;
use subxt::{
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
substitute_type(
type = "sp_runtime::per_things::Perbill<T, A>",
with = "::sp_runtime::Perbill"
)
)]
pub mod polkadot {}

#[tokio::main]
Expand Down

0 comments on commit 1a049b0

Please sign in to comment.