-
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
Avoid silent failure for associated types. #16
Conversation
/// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether scale_info::Path<PortableForm>
can also be a reference now? Not if you need to alter the path here for de-duping but possible if you've already de-duped by then perhaps?
@@ -90,12 +93,11 @@ fn types_equal_extended_to_params(a: &Type<PortableForm>, b: &Type<PortableForm> | |||
}; | |||
|
|||
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth unit testing this function?
@@ -81,7 +81,10 @@ 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 { | |||
pub(crate) fn types_equal_extended_to_params( |
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.
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 inner
would be pointing to a different type ID in either case, and require those types to be deduplicated into eg Foo
and Foo2
.
We could be smarter and notice that the T
in Foo<T>
is used as a param in Vec
and so we only need to output one type during codegen, but it's more complicated than just recursively calling types_equal_extended_to_params
because we need to track usages of the generic type, and I'm not sure if that's possible offhand?
Ie we could note that since T
has type ID 32, then any instances of that ID are actually instances of T being used, but that might not be the case (eg struct Bar<T = u32> { a: T, b: u32 }
). We could maybe use the typeName
if that is equal to T
too or something, in conjunction with the type ID? I'm curious if you have any ideas here @tadeohepperle!
All that said, for now it's perfeclty OK if it is more conservative as long as ensure_unique_type_paths
then uses this to rename the type paths appropriately. Better to have more duplicated types being spat out in the codegen than to assume types can be merged when in fact they can't!
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.
would be nice with some simple unit tests for fn types_equal_extended_to_params
as well, if we don't have it already.
} | ||
|
||
#[test] | ||
fn assoc_types_no_skip_params() { |
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!
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.
Looks good to me!
We can def work on making the type "de-duplication" stronger, but it's better to be more conservative rather than less; ultiamtely this just emits an error to warn users where ensure_type_path
isn't used and duple types exist, and fixes a small bug in the type comparison fn by the looks of it :)
When merging it with |
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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 comment
The reason will be displayed to describe this comment to others. Learn more.
So this will treat struct B<usize, String, bool> == struct A<(), (), usize>
because both contains usize
? Or am I missing something?
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.
seems like like a.name == b.name
is checked before this is called on composite/types should fine I think :)
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.
No, it is a bit more subtle: This ids_equal
function is called for every field of the type: Let's say we have a generic type like this:
struct A<T,U,V> {
f1: T,
f2: U,
f3: V
}
Let's say the concrete types A<usize, String, bool>
and A<(), (), usize>
have been registered in the type registry and the ids of the primitive types are: usize: id=0
, String: id=1
, bool: id=2
, (): id=3
. Then A<usize, String, bool>
is represented as:
NamedComposite<T: id=0, U: id=1, V: id=2> {
f1: id=0,
f2: id=1,
f3: id=2
}
and A<(), (), usize>
is represented as
NamedComposite<T: id=3, U: id=3, V: id=0> {
f1: id=3,
f2: id=3,
f3: id=0
}
Now we compare A<usize, String, bool>
and A<(), (), usize>
to find out if they are basically the same rust type (they should be, both are parameterizations of a generic A<T,U,V>
).
Now for field f1, we see that the type ids do not equal (0 != 3
). But: a_param_names
will say, oh type id=0 actually maps to [T]
and b_param_names
will say, oh type_id 3 maps to [T, or U]
.
Now the a_param_names.iter().any(|a_p| b_param_names.contains(a_p))
returns true, saying "okay this field is just the generic parameter T
for both registered types, so they are considered the same".
Then we continue with the next field f2
and so on.
Co-authored-by: Niklas Adolfsson <[email protected]>
Fixes #14
In the issue #14 it was raised that the type generation can currently override types with the same type path. This should only be valid if the generics of the two types match up, such that the types are essentially the same.
If you opt for deduplicating the type paths in the
PortableRegistry
withcrate::utils::ensure_unique_type_paths
this problem does not occur because then we generate two distinct types.This PR adds error checks during the type generation when two types with the same path are encountered. Previously we would have just overridden the first type.
I added a test to verify that the guards save us from the erroneous behavior reported in #14