-
Notifications
You must be signed in to change notification settings - Fork 248
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
codegen: Generate type aliases for better API ergonomics #1249
Conversation
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Something that would be interesting to do if you haven't already is to look at Erin's original sample code (from the issue) and see whether, when using this branch of Subxt, we can simplify the types nicely :) |
Looking at some random generated call signature from this: #[doc = "See [`Pallet::found_society`]."]
pub fn found_society(
&self,
founder: alias_types::found_society::Founder,
max_members: alias_types::found_society::MaxMembers,
max_intake: alias_types::found_society::MaxIntake,
max_strikes: alias_types::found_society::MaxStrikes,
candidate_deposit: alias_types::found_society::CandidateDeposit,
rules: alias_types::found_society::Rules,
) -> ::subxt::tx::Payload<types::FoundSociety> { I wonder about the separation between pub mod types {
pub mod found_society {
mod inputs {
type Founder = ...;
type MaxMembers = ...;
};
// This instead of `FoundSociety`
struct Input { ... }
}
} And then we'd have #[doc = "See [`Pallet::found_society`]."]
pub fn found_society(
&self,
founder: types::found_society::inputs::Founder,
max_members: types::found_society::inputs::MaxMembers,
max_intake: types::found_society::inputs::MaxIntake,
max_strikes: types::found_society::inputs::MaxStrikes,
candidate_deposit: types::found_society::inputs::CandidateDeposit,
rules: types::found_society::inputs::Rules,
) -> ::subxt::tx::Payload<types::found_society::Input> { Not sure; what do you think?! |
I think I like your idea even better, so having eg: pub mod types {
struct FoundSociety { ... }
pub mod found_society {
type Founder = ...;
type MaxMembers = ...;
}
} Less module indentation and maybe having the name of the extrinsic thing is quite nice actually anyway :) |
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
codegen/src/api/calls.rs
Outdated
// One downside of the type alias system is that we generate one alias per argument. | ||
// Multiple arguments could potentially have the same type (ie fn call(u32, u32, u32)). |
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.
That worried me for a sec, but actually all you mean is that we might have alias like
type Foo = core::primitive::u8;
type Bar = core::primitive::u8;
Which is fine I think :)
codegen/src/api/runtime_apis.rs
Outdated
@@ -11,6 +11,48 @@ use proc_macro2::TokenStream as TokenStream2; | |||
use quote::{format_ident, quote}; | |||
|
|||
/// Generates runtime functions for the given API metadata. | |||
/// | |||
/// # Note |
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.
This note is the sort of thing that could fall out of date fairly quickly, so personally I'd just be looking at the generated code anyway to see how things are structured and would avoid such a comment :)
codegen/src/api/runtime_apis.rs
Outdated
@@ -39,34 +81,53 @@ fn generate_runtime_api( | |||
let inputs: Vec<_> = method.inputs().enumerate().map(|(idx, input)| { | |||
// These are method names, which can just be '_', but struct field names can't | |||
// just be an underscore, so fix any such names we find to work in structs. | |||
let name = if input.name == "_" { | |||
format_ident!("_{}", idx) | |||
let (alias_name, name) = if input.name == "_" { |
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.
What about names that start with a _
, like _foo
? These would also be perfectly valid and we want to make sure they also work ok (a good opportunity for writing a test perhaps if one doesn't already exist)
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.
(and basically, can you guarantee that no combination of valid names will lead to an issue; eg you could transform _foo
into s_foo
before upper_camel_casing, but what if there is already an s_foo
? Unlikely I know, but would be good to be airtight if we can be :))
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, very nice to have these type aliases. Just one question: The type path of each type alias should always have all generic parameters fully filled in with concrete types, right? Like there are no type aliases that are generic over anything.
I haven't had a chance to properly look over this, but generally the aliases and their locations look good to me! One thing I'd like you to be sure of is that there are no names of calls/args/whatever that can lead to conflicts/compile errors, but otherwise I'd be happy for this to merge! |
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
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 think it is nice to have the aliases like this for now, however I think a lot of the structure of the code introduced in this PR will have to change, once scale-typegen
is introduced into subxt
. :)
@@ -180,7 +180,7 @@ pub mod tests { | |||
async fn test_commands() { | |||
// show pallets: | |||
let output = simulate_run("").await; | |||
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n ParaInherent\n Staking\n System\n Timestamp\n"); | |||
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n ParaInherent\n System\n Timestamp\n"); | |||
// if incorrect pallet, error: |
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 need to remind myself to make these updates in #1290 later, will probably have merge conflicts.
codegen/src/types/type_def.rs
Outdated
@@ -70,13 +71,16 @@ impl TypeDefGen { | |||
let docs = should_gen_docs.then_some(&*ty.docs).unwrap_or_default(); | |||
let composite_def = CompositeDef::struct_def( | |||
ty, | |||
types_mod_ident, | |||
&type_name, | |||
&type_name, |
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 we pass the type_name as the variant_name
parameter here as well, but it is not really used for a normal Composite from the types registry, right?
codegen/src/types/composite_def.rs
Outdated
ident: &str, | ||
variant_name: &str, |
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 find it a little bit confusing to add a variant_name
field here, which will only have a special meaning for call types etc... I suppose for most types this will be the same as the ident?
Also I think we should maybe introduce a new struct for the type alias functionality instead of cramping it all into a CompositeDef.
But maybe I don't understand enough why these decisions are made.
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 a bit scary to me that this already long list of arguments has grown by 3. It may be trying to do too many things now.
I wonder how easy it would be to separate the logic to generate the structs with aliases that we need in the "subxt codegen" side, and try to minimise how much we touch this types
folder (which will be going away soon when scale-typegen
is used instead)?
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.
Yep that makes sense!
I've split the implementation to rely on TypeAliases
for generating the type alias module.
On the CompositeDef
, we still need a bit of information wrts whatever to generate the structure from scratch (needed for generating all types from the registry); or to generate the structure with type aliases (calls / events).
On that note, we'd probably need to think about integration with scale-typegen wrts:
- the current
CompositeDefFields
has ato_type_aliases_tokens
which generates aliases from the fields of a structure to_struct_field_tokens
is adjusted to conditionally use the aliases or the types directly
@tadeohepperle let me know if this is right
Do you mean that when |
I am certain that once we use |
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
@@ -160,3 +204,225 @@ pub fn generate_runtime_apis( | |||
} | |||
}) | |||
} | |||
|
|||
#[cfg(test)] | |||
mod tests { |
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.
Nice tests! Def gives me some confidence in the param naming logic :)
@@ -210,11 +259,38 @@ impl CompositeDefFields { | |||
} | |||
} | |||
|
|||
/// Generate the code for type aliases which will be used to construct the `struct` or `enum`. | |||
pub fn to_type_aliases_tokens(&self, visibility: Option<&syn::Visibility>) -> TokenStream { |
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 still feel uneasy about this and the below alias_module_name
additions (which, if provided, basically falls into if branches which do something that takes up most of the logic there).
I wonder whether this alias stuff should be more completely separate from the usual type generation stuff in this types
folder, so that the types
folder is relatively untouched in this PR (and can be swapped out for scale-typegen
fairly easily to do the standard type generation stuff), and the alias stuff lives elsewhere (maybe a separate generate_alias.rs
file or something outside of this folder) and is quite independent?
I'm not sure whether that would lead to a bunch of code duplication though, or whether the code for aliases is different enough that there's actually not a lot of overlap (which is just the feeling I get looking at the below).
That all said, @tadeohepperle what are your thoughts on this? Does adding these bits complicate moving to scale-typegen
? We can also think about it in a separate PR of course too, since I know that this one has been lingering a while!
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 think it will be fine to move the to_type_alias_tokens
into a standalone function later when integrating scale-typegen
(because CompositeDefFields
will no longer exist). Let's merge this and I will figure out the conflicts things out in this PR: #1260
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'm inclined to approve this because while I wonder about the alias logic being mixed in with the "normal" type gen logic, we can address that separately, and overall I really like the codegen that this adds, and it'll def make some things much nicer :)
Signed-off-by: Alexandru Vasile <[email protected]>
This PR generates type aliases for:
Check the
runtime_api
section for a common type that users had to manually define in their application if they wanted to store the type.Storage
Type aliases are placed under the
types
module.In the case of storage entries, inner modules are named by the storage entry.
They contain the type alias of the storage result, as well as type aliases for all parameters needed for partial iteration.
Because those parameters are unnamed, they follow
Param {index}
naming convention.Calls
Type aliases are placed under the already generated
types
module.The structures generated inside the
types
module are modified to use the type aliases for each structure field.A module with the snake case name convention is generated next to the structure name and contains all the type aliased fields.
Events
The generation is similar to calls, where the type aliases are placed in a module next to the generated structure of the event.
Runtime APIs
The
types
module of the runtime API is extended for each runtime API function with an aliased module containing the runtime API's output, together with individual input structure type aliases.Notes
_2: impl ::std::borrow::Borrow< runtime_types::bounded_collections::bounded_vec::BoundedVec< ::core::primitive::u8>,