Skip to content
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

portable: Retain the provided type IDs #174

Merged
merged 30 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e3e2312
ty: Make type fields public
lexnv Mar 8, 2023
8143c65
portable: Add recursive type ID resolver
lexnv Mar 8, 2023
f18bafa
tests: Check recursive type ID correctness
lexnv Mar 8, 2023
e5dc1e2
portable: Implement `retain` on the `PortableRegistry`
lexnv Mar 8, 2023
965579e
tests: Check `retain` method
lexnv Mar 8, 2023
d1e7773
portable: Add error type
lexnv Mar 8, 2023
49be10d
derive: Fix clippy and improve documentation
lexnv Mar 8, 2023
f1a378b
Fix clippy
lexnv Mar 9, 2023
3fdf492
rustfmt: Change deprecated fmt
lexnv Mar 9, 2023
867b926
portable: Ensure no-std works
lexnv Mar 9, 2023
7241cc8
test_suite: Adjust UI tests to latest
lexnv Mar 9, 2023
8ee93db
Adjust UI tests with nightly
lexnv Mar 9, 2023
4264969
portable: Remove AtomicU32
lexnv Mar 13, 2023
4549555
portable: Use DFS for recursive ID assignment
lexnv Mar 13, 2023
5d70abb
ty: Implement zero-alloc Default::default() for Type
lexnv Mar 17, 2023
5071adf
portable: Change `retain` signature with FnMut
lexnv Mar 17, 2023
863f06b
portable/tests: Apply clippy
lexnv Mar 17, 2023
d5123ea
git: Use clippy with `--all-targets`
lexnv Mar 17, 2023
ee3ae6d
ty: Make fields public to this crate `pub(crate)` only
lexnv Mar 17, 2023
47859ae
portable: Use `mem` from `prelude` and not from `std`
lexnv Mar 17, 2023
b04048b
retain
jsdw Mar 17, 2023
dabb640
portable: Modify docs and apply fmt
lexnv Mar 17, 2023
17a98f2
portable/test: Check result BTreeMap
lexnv Mar 17, 2023
9bcb199
Fix cargo check
lexnv Mar 17, 2023
16011f5
tests: Recursive retain for all type-def types
lexnv Mar 21, 2023
5701df5
ty: Use placeholder instead of implementing dummy Default::default()
lexnv Mar 21, 2023
3000e18
portable: use crate::prelude::vec![] insted of vec![]
lexnv Mar 21, 2023
95fa6cb
Update src/portable.rs
lexnv Mar 21, 2023
72ac00d
portable: Fix clippy
lexnv Mar 21, 2023
e4018f2
Update src/portable.rs
lexnv Mar 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: clippy
run: |
cargo clippy --version
cargo clippy --all -- -D warnings
cargo clippy --all-targets -- -D warnings

- name: check-all-features
run: |
Expand Down
2 changes: 1 addition & 1 deletion .rustfmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = true
force_multiline_blocks = true # changed
fn_args_layout = "Tall"
fn_params_layout = "Tall"
brace_style = "SameLineWhere"
control_brace_style = "AlwaysSameLine"
trailing_semicolon = false # changed
Expand Down
2 changes: 1 addition & 1 deletion derive/src/trait_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pub fn make_where_clause<'a>(
}
});

generics.type_params().into_iter().for_each(|type_param| {
generics.type_params().for_each(|type_param| {
let ident = type_param.ident.clone();
let mut bounds = type_param.bounds.clone();
if attrs
Expand Down
243 changes: 242 additions & 1 deletion src/portable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ use crate::{
form::PortableForm,
interner::Interner,
prelude::{
collections::BTreeMap,
fmt::Debug,
mem,
vec::Vec,
},
Registry,
Type,
TypeDef,
};
use scale::Encode;

Expand Down Expand Up @@ -70,13 +73,154 @@ impl PortableRegistry {
pub fn types(&self) -> &[PortableType] {
&self.types
}

/// Retains only the portable types needed to express the provided ids.
///
/// The type IDs retained are returned as key to the `HashMap`.
lexnv marked this conversation as resolved.
Show resolved Hide resolved
/// The value of the map represents the new ID of that type.
///
/// # Note
///
/// A given type ID can be defined by nesting type IDs, such as the case
/// of a [`TypeDef::Composite`] and others. To retain a valid [`PortableRegistry`]
/// all the types needed to express an ID are included. Therefore, the number of
/// elements defined by the result equals or exceeds the number of provided IDs.
pub fn retain<F>(&mut self, mut filter: F) -> BTreeMap<u32, u32>
where
F: FnMut(u32) -> bool,
{
let mut retained_mappings = BTreeMap::new();
let mut new_types = crate::prelude::vec![];

fn retain_type(
id: u32,
types: &mut [PortableType],
new_types: &mut Vec<PortableType>,
retained_mappings: &mut BTreeMap<u32, u32>,
) -> u32 {
// Type already retained; just return the new ID for it:
if let Some(id) = retained_mappings.get(&id) {
return *id
}

// Take the type out of the registry that we'll be retaining:
let mut ty = mem::take(&mut types[id as usize]);

// Make sure any type params are also retained:
for param in ty.ty.type_params.iter_mut() {
let Some(ty) = param.ty() else {
continue
};
let new_id = retain_type(ty.id(), types, new_types, retained_mappings);
param.ty = Some(new_id).map(Into::into);
}

// make sure any types inside this type are also retained and update the IDs:
match &mut ty.ty.type_def {
TypeDef::Composite(composite) => {
for field in composite.fields.iter_mut() {
let new_id = retain_type(
field.ty.id(),
types,
new_types,
retained_mappings,
);
field.ty = new_id.into();
}
}
TypeDef::Variant(variant) => {
for var in variant.variants.iter_mut() {
for field in var.fields.iter_mut() {
let new_id = retain_type(
field.ty.id(),
types,
new_types,
retained_mappings,
);
field.ty = new_id.into();
}
}
}
TypeDef::Sequence(sequence) => {
let new_id = retain_type(
sequence.type_param.id(),
types,
new_types,
retained_mappings,
);
sequence.type_param = new_id.into();
}
TypeDef::Array(array) => {
let new_id = retain_type(
array.type_param.id(),
types,
new_types,
retained_mappings,
);
array.type_param = new_id.into();
}
TypeDef::Tuple(tuple) => {
for ty in tuple.fields.iter_mut() {
let new_id =
retain_type(ty.id(), types, new_types, retained_mappings);
*ty = new_id.into();
}
}
TypeDef::Primitive(_) => (),
TypeDef::Compact(compact) => {
let new_id = retain_type(
compact.type_param().id(),
types,
new_types,
retained_mappings,
);
compact.type_param = new_id.into();
}
TypeDef::BitSequence(bit_seq) => {
let bit_order_id = retain_type(
bit_seq.bit_order_type().id(),
types,
new_types,
retained_mappings,
);
let bit_store_id = retain_type(
bit_seq.bit_store_type().id(),
types,
new_types,
retained_mappings,
);

bit_seq.bit_order_type = bit_order_id.into();
bit_seq.bit_store_type = bit_store_id.into();
}
}

// Retain this type, having updated any inner IDs:
let new_id = new_types.len() as u32;
new_types.push(ty);
retained_mappings.insert(id, new_id);
new_id
}

for id in 0..self.types.len() as u32 {
// We don't care about the type; move on:
if !filter(id) {
continue
}

retain_type(id, &mut self.types, &mut new_types, &mut retained_mappings);
}

self.types = new_types;
retained_mappings
}
}

/// Represent a type in it's portable form.
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(all(feature = "serde", feature = "decode"), derive(serde::Deserialize))]
#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))]
#[derive(Clone, Debug, PartialEq, Eq, Encode)]
#[derive(Clone, Debug, PartialEq, Eq, Encode, Default)]
pub struct PortableType {
#[codec(compact)]
id: u32,
Expand Down Expand Up @@ -218,4 +362,101 @@ mod tests {
assert_eq!(Some(&vec_u32_type), registry.resolve(vec_u32_type_id));
assert_eq!(Some(&composite_type), registry.resolve(composite_type_id));
}

#[test]
fn retain_ids() {
let mut builder = PortableRegistryBuilder::new();
let u32_type = Type::new(Path::default(), vec![], TypeDefPrimitive::U32, vec![]);
let _u32_type_id = builder.register_type(u32_type);

let u64_type = Type::new(Path::default(), vec![], TypeDefPrimitive::U64, vec![]);
let u64_type_id = builder.register_type(u64_type.clone());

let mut registry = builder.finish();
assert_eq!(registry.types.len(), 2);

let ids_result = registry.retain(|id| id == u64_type_id);
assert_eq!(ids_result.len(), 1);
assert_eq!(ids_result.get(&u64_type_id), Some(&0));

assert_eq!(registry.types.len(), 1);
assert_eq!(registry.resolve(0).unwrap(), &u64_type);
}

#[test]
fn retain_recursive_ids() {
let mut builder = PortableRegistryBuilder::new();
let u32_type = Type::new(Path::default(), vec![], TypeDefPrimitive::U32, vec![]);
let u32_type_id = builder.register_type(u32_type.clone());

let u64_type = Type::new(Path::default(), vec![], TypeDefPrimitive::U64, vec![]);
let _u64_type_id = builder.register_type(u64_type);

let vec_u32_type = Type::new(
Path::default(),
vec![],
TypeDefSequence::new(u32_type_id.into()),
vec![],
);
let vec_u32_type_id = builder.register_type(vec_u32_type);

let composite_type = Type::builder_portable()
.path(Path::from_segments_unchecked(["MyStruct".into()]))
.composite(
Fields::named()
.field_portable(|f| f.name("primitive".into()).ty(u32_type_id))
.field_portable(|f| f.name("vec_of_u32".into()).ty(vec_u32_type_id)),
);
let composite_type_id = builder.register_type(composite_type);

let composite_type_second = Type::builder_portable()
.path(Path::from_segments_unchecked(["MyStructSecond".into()]))
.composite(
Fields::named()
.field_portable(|f| f.name("vec_of_u32".into()).ty(vec_u32_type_id))
.field_portable(|f| f.name("second".into()).ty(composite_type_id)),
);
let composite_type_second_id = builder.register_type(composite_type_second);

let mut registry = builder.finish();
assert_eq!(registry.types.len(), 5);

let ids_result = registry.retain(|id| id == composite_type_second_id);
assert_eq!(ids_result.len(), 4);
assert_eq!(ids_result.get(&u32_type_id), Some(&0));
assert_eq!(ids_result.get(&vec_u32_type_id), Some(&1));
assert_eq!(ids_result.get(&composite_type_id), Some(&2));
assert_eq!(ids_result.get(&composite_type_second_id), Some(&3));

assert_eq!(registry.types.len(), 4);

// New type IDs are generated in DFS manner.
assert_eq!(registry.resolve(0).unwrap(), &u32_type);

let expected_type = Type::new(
Path::default(),
vec![],
TypeDefSequence::new(0.into()),
vec![],
);
assert_eq!(registry.resolve(1).unwrap(), &expected_type);

let expected_type = Type::builder_portable()
.path(Path::from_segments_unchecked(["MyStruct".into()]))
.composite(
Fields::named()
.field_portable(|f| f.name("primitive".into()).ty(0))
.field_portable(|f| f.name("vec_of_u32".into()).ty(1)),
);
assert_eq!(registry.resolve(2).unwrap(), &expected_type);

let expected_type = Type::builder_portable()
.path(Path::from_segments_unchecked(["MyStructSecond".into()]))
.composite(
Fields::named()
.field_portable(|f| f.name("vec_of_u32".into()).ty(1))
.field_portable(|f| f.name("second".into()).ty(2)),
);
assert_eq!(registry.resolve(3).unwrap(), &expected_type);
}
}
2 changes: 1 addition & 1 deletion src/ty/composite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub struct TypeDefComposite<T: Form = MetaForm> {
feature = "serde",
serde(skip_serializing_if = "Vec::is_empty", default)
)]
fields: Vec<Field<T>>,
pub(crate) fields: Vec<Field<T>>,
}

impl IntoPortable for TypeDefComposite {
Expand Down
8 changes: 4 additions & 4 deletions src/ty/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,22 @@ pub struct Field<T: Form = MetaForm> {
feature = "serde",
serde(skip_serializing_if = "Option::is_none", default)
)]
name: Option<T::String>,
pub(crate) name: Option<T::String>,
/// The type of the field.
#[cfg_attr(feature = "serde", serde(rename = "type"))]
ty: T::Type,
pub(crate) ty: T::Type,
/// The name of the type of the field as it appears in the source code.
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "Option::is_none", default)
)]
type_name: Option<T::String>,
pub(crate) type_name: Option<T::String>,
/// Documentation
#[cfg_attr(
feature = "serde",
serde(skip_serializing_if = "Vec::is_empty", default)
)]
docs: Vec<T::String>,
pub(crate) docs: Vec<T::String>,
}

impl IntoPortable for Field {
Expand Down
Loading