-
Notifications
You must be signed in to change notification settings - Fork 0
Avoid silent failure for associated types. #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4cc28e5
176df5a
c917d1f
0fdae7f
8ae98a4
836adb2
8d4599e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,23 +9,24 @@ use scale_info::form::PortableForm; | |
|
||
/// Represents a Rust `mod`, containing generated types and child `mod`s. | ||
#[derive(Debug, Clone)] | ||
pub struct ModuleIR { | ||
pub struct ModuleIR<'a> { | ||
/// Name of this module. | ||
pub name: Ident, | ||
/// Root module identifier. | ||
pub root_mod: Ident, | ||
/// Submodules of this module. | ||
pub children: BTreeMap<Ident, ModuleIR>, | ||
pub children: BTreeMap<Ident, ModuleIR<'a>>, | ||
/// Types in this module. | ||
pub types: BTreeMap<scale_info::Path<PortableForm>, TypeIR>, | ||
pub types: | ||
BTreeMap<scale_info::Path<PortableForm>, (&'a scale_info::Type<PortableForm>, TypeIR)>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder whether |
||
} | ||
|
||
impl ToTokens for ModuleIR { | ||
impl<'a> ToTokens for ModuleIR<'a> { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
let name = &self.name; | ||
let root_mod = &self.root_mod; | ||
let modules = self.children.values(); | ||
let types = self.types.values().clone(); | ||
let types = self.types.values().map(|e| &e.1).clone(); | ||
|
||
tokens.extend(quote! { | ||
pub mod #name { | ||
|
@@ -38,7 +39,7 @@ impl ToTokens for ModuleIR { | |
} | ||
} | ||
|
||
impl ModuleIR { | ||
impl<'a> ModuleIR<'a> { | ||
/// Create a new [`Module`], with a reference to the root `mod` for resolving type paths. | ||
pub(crate) fn new(name: Ident, root_mod: Ident) -> Self { | ||
Self { | ||
|
@@ -61,7 +62,7 @@ impl ModuleIR { | |
|
||
/// Returns the generated types. | ||
pub fn types(&self) -> impl Iterator<Item = (&scale_info::Path<PortableForm>, &TypeIR)> { | ||
self.types.iter() | ||
self.types.iter().map(|(k, v)| (k, &v.1)) | ||
} | ||
|
||
/// Returns the root `mod` used for resolving type paths. | ||
|
@@ -71,7 +72,7 @@ impl ModuleIR { | |
|
||
/// Recursively creates submodules for the given namespace and returns a mutable reference to the innermost module created this way. | ||
/// Returns itself, if the namespace is empty. | ||
pub fn get_or_insert_submodule(&mut self, namespace: &[String]) -> &mut ModuleIR { | ||
pub fn get_or_insert_submodule(&mut self, namespace: &[String]) -> &mut ModuleIR<'a> { | ||
if namespace.is_empty() { | ||
return self; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
use crate::TypegenError; | ||
use std::collections::btree_map::Entry; | ||
|
||
use crate::{utils::types_equal_extended_to_params, TypegenError}; | ||
|
||
use self::{ | ||
ir::module_ir::ModuleIR, | ||
|
@@ -89,10 +91,24 @@ impl<'a> TypeGenerator<'a> { | |
} | ||
|
||
// if the type is not a builtin type, insert it into the respective module | ||
if let Some(type_ir) = self.create_type_ir(&ty.ty, &flat_derives_registry)? { | ||
let ty = &ty.ty; | ||
if let Some(type_ir) = self.create_type_ir(ty, &flat_derives_registry)? { | ||
// Create the module this type should go into | ||
let innermost_module = root_mod.get_or_insert_submodule(namespace); | ||
innermost_module.types.insert(path.clone(), type_ir); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
match innermost_module.types.entry(path.clone()) { | ||
Entry::Vacant(e) => { | ||
e.insert((ty, type_ir)); | ||
} | ||
Entry::Occupied(e) => { | ||
// There is already a type with the same type path present. | ||
// We do not just want to override it, so we check if the two types are semantically similar (structure + generics). | ||
// If not, return an error, if yes, just keep the first one. | ||
let other_ty = &e.get().0; | ||
if !types_equal_extended_to_params(ty, other_ty) { | ||
return Err(TypegenError::DuplicateTypePath(ty.path.to_string())); | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
use scale_info::{form::PortableForm, Field, PortableRegistry, Type, TypeDef, TypeParameter}; | ||
use std::collections::HashMap; | ||
use smallvec::{smallvec, SmallVec}; | ||
use std::collections::{hash_map::Entry, HashMap}; | ||
|
||
use crate::TypegenError; | ||
|
||
|
@@ -81,25 +82,50 @@ pub fn ensure_unique_type_paths(types: &mut PortableRegistry) { | |
/// all type ids mentioned in the TypeDef are either: | ||
/// - equal | ||
/// - or different, but map essentially to the same generic type parameter | ||
fn types_equal_extended_to_params(a: &Type<PortableForm>, b: &Type<PortableForm>) -> bool { | ||
let collect_params = |type_params: &[TypeParameter<PortableForm>]| { | ||
type_params | ||
.iter() | ||
.filter_map(|p| p.ty.map(|ty| (ty.id, p.name.clone()))) | ||
.collect::<HashMap<u32, String>>() | ||
}; | ||
pub(crate) fn types_equal_extended_to_params( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So right now this function Does a shallow compare, and to use an earlier example an issue is eg if we compare: struct Foo<T = u64> {
inner: Vec<u64>
}
struct Foo<T = u32> {
inner: Vec<u32>
} We would return false because We could be smarter and notice that the Ie we could note that since All that said, for now it's perfeclty OK if it is more conservative as long as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be nice with some simple unit tests for |
||
a: &Type<PortableForm>, | ||
b: &Type<PortableForm>, | ||
) -> bool { | ||
// We map each type ID to all type params if could refer to. Type IDs can refer to multiple parameters: | ||
// E.g. Foo<A,B> can be parameterized as Foo<u8,u8>, so if 42 is the type id of u8, a field with id=42 could refer to either A or B. | ||
fn collect_params( | ||
type_params: &[TypeParameter<PortableForm>], | ||
) -> HashMap<u32, SmallVec<[&str; 2]>> { | ||
let mut hm: HashMap<u32, SmallVec<[&str; 2]>> = HashMap::new(); | ||
for p in type_params { | ||
if let Some(ty) = &p.ty { | ||
match hm.entry(ty.id) { | ||
Entry::Occupied(mut e) => { | ||
e.get_mut().push(p.name.as_str()); | ||
} | ||
Entry::Vacant(e) => { | ||
e.insert(smallvec![p.name.as_str()]); | ||
} | ||
} | ||
} | ||
} | ||
hm | ||
} | ||
|
||
let type_params_a = collect_params(&a.type_params); | ||
let type_params_b = collect_params(&a.type_params); | ||
let type_params_b = collect_params(&b.type_params); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hehe whoops :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth unit testing this function? |
||
|
||
if type_params_a.len() != type_params_b.len() { | ||
if a.type_params.len() != b.type_params.len() { | ||
return false; | ||
} | ||
|
||
// returns true if the ids are the same OR if they point to the same generic parameter. | ||
// Returns true if the ids are the same OR if they point to the same generic parameter. | ||
let ids_equal = |a: u32, b: u32| -> bool { | ||
a == b | ||
|| matches!((type_params_a.get(&a), type_params_b.get(&b)), (Some(a_name), Some(b_name)) if a_name == b_name) | ||
if a == b { | ||
return true; | ||
} | ||
let Some(a_param_names) = type_params_a.get(&a) else { | ||
return false; | ||
}; | ||
let Some(b_param_names) = type_params_b.get(&b) else { | ||
return false; | ||
}; | ||
// Check if there is any intersection, meaning that both IDs map to the same generic type param: | ||
a_param_names.iter().any(|a_p| b_param_names.contains(a_p)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this will treat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it is a bit more subtle: This
Let's say the concrete types
and
Now we compare |
||
}; | ||
|
||
let fields_equal = |a: &[Field<PortableForm>], b: &[Field<PortableForm>]| -> bool { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to cover this with a test!