From 8b116d1d51bc8b7f495535c73f82e71553dc6f53 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 1 Dec 2025 20:16:01 +0100 Subject: [PATCH 01/16] wip: type resolver, generic resolver, builder --- Cargo.lock | 3 + rs/idl-gen/Cargo.toml | 5 +- rs/idl-gen/hbs/composite.hbs | 11 - rs/idl-gen/hbs/idl.hbs | 65 -- rs/idl-gen/hbs/variant.hbs | 31 - rs/idl-gen/src/builder.rs | 268 ++++++++ rs/idl-gen/src/generic_resolver.rs | 321 +++++++++ rs/idl-gen/src/lib.rs | 113 ++-- rs/idl-gen/src/type_resolver.rs | 615 ++++++++++++++++++ ...r__program_idl_works_with_empty_ctors.snap | 185 +++--- ...gram_idl_works_with_multiple_services.snap | 291 +++++---- ...rogram_idl_works_with_non_empty_ctors.snap | 200 +++--- ..._service_idl_works_with_base_services.snap | 187 +++--- ...erator__service_idl_works_with_basics.snap | 181 +++--- 14 files changed, 1749 insertions(+), 727 deletions(-) delete mode 100644 rs/idl-gen/hbs/composite.hbs delete mode 100644 rs/idl-gen/hbs/idl.hbs delete mode 100644 rs/idl-gen/hbs/variant.hbs create mode 100644 rs/idl-gen/src/builder.rs create mode 100644 rs/idl-gen/src/generic_resolver.rs create mode 100644 rs/idl-gen/src/type_resolver.rs diff --git a/Cargo.lock b/Cargo.lock index 3cd62a4ce..9d22a19b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7228,15 +7228,18 @@ dependencies = [ name = "sails-idl-gen" version = "0.9.2" dependencies = [ + "askama", "convert_case 0.7.1", "gprimitives", "handlebars", "insta", + "quote", "sails-idl-meta", "sails-idl-parser", "scale-info", "serde", "serde_json", + "syn 2.0.104", "thiserror 2.0.12", ] diff --git a/rs/idl-gen/Cargo.toml b/rs/idl-gen/Cargo.toml index 6bbd529d9..cb33971d4 100644 --- a/rs/idl-gen/Cargo.toml +++ b/rs/idl-gen/Cargo.toml @@ -10,14 +10,17 @@ repository.workspace = true rust-version.workspace = true [dependencies] +askama.workspace = true convert_case.workspace = true gprimitives.workspace = true handlebars.workspace = true -sails-idl-meta.workspace = true +sails-idl-meta = { workspace = true, features = ["ast", "templates"] } scale-info = { workspace = true, features = ["derive", "docs", "serde"] } serde = { workspace = true, features = ["derive"] } serde-json.workspace = true thiserror.workspace = true +syn = { workspace = true, features = ["parsing", "full", "fold"] } +quote.workspace = true [dev-dependencies] insta.workspace = true diff --git a/rs/idl-gen/hbs/composite.hbs b/rs/idl-gen/hbs/composite.hbs deleted file mode 100644 index 15cd6492f..000000000 --- a/rs/idl-gen/hbs/composite.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{#each docs}} -/// {{{this}}} -{{/each}} -type {{{lookup @root/type_names id}}} = struct { - {{#each fields}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{#if name}}{{name}}: {{/if~}}{{{lookup @root/type_names type}}}, - {{/each}} -}; diff --git a/rs/idl-gen/hbs/idl.hbs b/rs/idl-gen/hbs/idl.hbs deleted file mode 100644 index 94711ef61..000000000 --- a/rs/idl-gen/hbs/idl.hbs +++ /dev/null @@ -1,65 +0,0 @@ -{{#each types as |type|}} - {{#each type.type.def}} - {{~> (deref @key) id=type.id docs=type.type.docs}} - {{/each}} - -{{/each}} -{{#if (len ctors)}} -constructor { -{{#each ctors}} - {{#each ./[2]}} - /// {{{this}}} - {{/each}} - {{./[0]}} : ({{#each ./[1]}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}); -{{/each}} -}; - -{{/if}} -{{#each services}} -service {{#if name}}{{name}} {{/if}}{ -{{#each commands}} - {{#each ./[3]}} - /// {{{this}}} - {{/each}} - {{./[0]}} : ({{#each ./[1]}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}) -> {{{lookup @root/type_names ./[2]}}}; -{{/each}} -{{#each queries}} - {{#each ./[3]}} - /// {{{this}}} - {{/each}} - query {{./[0]}} : ({{#each ./[1]}}{{#if @index}}, {{/if}}{{name}}: {{{lookup @root/type_names type}}}{{/each}}) -> {{{lookup @root/type_names ./[2]}}}; -{{/each}} -{{#if (len events)}} - - events { - {{#each events}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}} - {{~#if fields.[1]}}: struct { - {{#each fields}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{#if name}}{{name}}: {{/if~}}{{{lookup @root/type_names type}}}, - {{/each}} - } {{~else}} - {{~#if fields.[0]}}: {{#with fields.[0]}} {{~#if name~}} struct { - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}}: {{{lookup @root/type_names type}}} - }{{else~}} - {{{lookup @root/type_names type}}} - {{~/if}} - {{~/with~}} - {{~/if~}} - {{~/if~}} - ; - {{/each}} - } -{{/if}} -}; - -{{/each}} \ No newline at end of file diff --git a/rs/idl-gen/hbs/variant.hbs b/rs/idl-gen/hbs/variant.hbs deleted file mode 100644 index 672df3f05..000000000 --- a/rs/idl-gen/hbs/variant.hbs +++ /dev/null @@ -1,31 +0,0 @@ -{{#each docs}} -/// {{{this}}} -{{/each}} -type {{{lookup @root/type_names id}}} = enum { -{{#each variants}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}} - {{~#if fields.[1]}}: struct { - {{#each fields~}} - {{#each docs}} - /// {{{this}}} - {{/each}} - {{#if name}}{{name}}: {{/if~}}{{{lookup @root/type_names type}}}, - {{/each}} - } {{~else}} - {{~#if fields.[0]}}: {{#with fields.[0]}} {{~#if name~}} struct { - {{#each docs}} - /// {{{this}}} - {{/each}} - {{name}}: {{{lookup @root/type_names type}}} - }{{else~}} - {{{lookup @root/type_names type}}} - {{~/if}} - {{~/with~}} - {{~/if~}} - {{~/if~}} - , -{{/each}} -}; diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs new file mode 100644 index 000000000..f3709b8c8 --- /dev/null +++ b/rs/idl-gen/src/builder.rs @@ -0,0 +1,268 @@ +use super::*; +use crate::type_resolver::TypeResolver; +use scale_info::*; +use std::collections::HashSet; + +pub struct ProgramBuilder { + registry: PortableRegistry, + ctors_type_id: u32, // ctor_fns: Vec, + services_expo: Vec, +} + +impl ProgramBuilder { + pub fn new() -> Self { + let ctors = P::constructors(); + let mut registry = Registry::new(); + let ctors_type_id = registry.register_type(&ctors).id; + let services_expo = P::services() + .map(|(name, _meta)| ServiceExpo { + name: name.to_string(), + route: None, + docs: vec![], + annotations: vec![], + }) + .collect::>(); + let registry = PortableRegistry::from(registry); + Self { + registry, + ctors_type_id, + services_expo, + } + } + + fn ctor_funcs(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.ctors_type_id)? + .map(|c| { + assert_eq!(1, c.fields.len()); + let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + Ok(CtorFunc { + name: c.name.to_string(), + params: params_type + .fields + .iter() + .map(|f| FuncParam { + name: f.name.unwrap().to_string(), + type_decl: resolver.get(f.ty.id).unwrap().clone(), + }) + .collect(), + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![], + }) + } else { + unreachable!() + } + }) + .collect() + } + + fn services_expo(&self) -> &Vec { + &self.services_expo + } + + pub fn build(self, name: String) -> ProgramUnit { + let mut exclude = HashSet::new(); + exclude.insert(self.ctors_type_id); + exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)); + let resolver = TypeResolver::from(&self.registry, exclude); + let ctors = self.ctor_funcs(&resolver).unwrap(); + let services = self.services_expo().clone(); + let types = resolver.into_types(); + + ProgramUnit { + name, + ctors, + services, + types, + docs: vec![], + annotations: vec![], + } + } +} + +fn any_funcs( + registry: &PortableRegistry, + func_type_id: u32, +) -> Result>> { + let funcs = registry.resolve(func_type_id).unwrap_or_else(|| { + panic!("func type id {func_type_id} not found while it was registered previously") + }); + if let scale_info::TypeDef::Variant(variant) = &funcs.type_def { + Ok(variant.variants.iter()) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "func type id {func_type_id} references a type that is not a variant" + ))) + } +} + +fn any_funcs_ids(registry: &PortableRegistry, func_type_id: u32) -> impl Iterator { + let funcs = registry.resolve(func_type_id).unwrap(); + if let scale_info::TypeDef::Variant(variant) = &funcs.type_def { + variant.variants.iter().map(|v| v.fields[0].ty.id) + } else { + unreachable!() + } +} + +fn flat_meta( + service_meta: &AnyServiceMeta, + meta: fn(&AnyServiceMeta) -> &MetaType, +) -> Vec<&MetaType> { + let mut metas = vec![meta(service_meta)]; + for base_service_meta in service_meta.base_services() { + metas.extend(flat_meta(base_service_meta, meta)); + } + metas +} + +pub struct ServiceBuilder { + name: &'static str, + registry: PortableRegistry, + commands_type_id: u32, + queries_type_id: u32, + events_type_id: u32, +} + +impl ServiceBuilder { + pub fn new(name: &'static str, meta: AnyServiceMeta) -> Self { + let mut registry = Registry::new(); + let commands_type_id = registry.register_type(&meta.commands()).id; + let queries_type_id = registry.register_type(&meta.queries()).id; + let events_type_id = registry.register_type(&meta.events()).id; + let registry = PortableRegistry::from(registry); + Self { + name, + registry, + commands_type_id, + queries_type_id, + events_type_id, + } + } + + pub fn build(self) -> ServiceUnit { + let mut exclude = HashSet::new(); + exclude.insert(self.commands_type_id); + exclude.extend(any_funcs_ids(&self.registry, self.commands_type_id)); + exclude.insert(self.queries_type_id); + exclude.extend(any_funcs_ids(&self.registry, self.queries_type_id)); + exclude.insert(self.events_type_id); + let resolver = TypeResolver::from(&self.registry, exclude); + let commands = self.commands(&resolver).unwrap(); + let queries = self.queries(&resolver).unwrap(); + let events = self.events(&resolver).unwrap(); + // let extends = self.extends(); + // let services = self.services_expo().clone(); + let types = resolver.into_types(); + + ServiceUnit { + name: self.name.to_string(), + extends: vec![], + funcs: [commands, queries].concat(), + events, + types, + docs: vec![], + annotations: vec![], + } + } + + fn commands(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.commands_type_id)? + .map(|c| { + assert_eq!(2, c.fields.len()); + let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); + let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); + let mut throws = None; + // TODO: unwrap result param + if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { + output = ok; + throws = Some(err); + }; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + Ok(ServiceFunc { + name: c.name.to_string(), + params: params_type + .fields + .iter() + .map(|f| FuncParam { + name: f.name.unwrap().to_string(), + type_decl: resolver.get(f.ty.id).unwrap().clone(), + }) + .collect(), + output, + throws, + kind: FunctionKind::Command, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![], + }) + } else { + unreachable!() + } + }) + .collect() + } + + fn queries(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.queries_type_id)? + .map(|c| { + assert_eq!(2, c.fields.len()); + let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); + let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); + let mut throws = None; + // TODO: unwrap result param + if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { + output = ok; + throws = Some(err); + }; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + Ok(ServiceFunc { + name: c.name.to_string(), + params: params_type + .fields + .iter() + .map(|f| FuncParam { + name: f.name.unwrap().to_string(), + type_decl: resolver.get(f.ty.id).unwrap().clone(), + }) + .collect(), + output, + // TODO: Throws type + throws, + kind: FunctionKind::Query, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![("query".to_string(), None)], + }) + } else { + unreachable!() + } + }) + .collect() + } + + fn events(&self, resolver: &TypeResolver) -> Result> { + any_funcs(&self.registry, self.events_type_id)? + .map(|v| { + let fields = v + .fields + .iter() + .map(|field| { + let type_decl = resolver.get(field.ty.id).unwrap().clone(); + StructField { + name: field.name.map(|s| s.to_string()), + type_decl, + docs: field.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], + } + }) + .collect(); + + Ok(ServiceEvent { + name: v.name.to_string(), + def: StructDef { fields }, + docs: v.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], + }) + }) + .collect() + } +} diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs new file mode 100644 index 000000000..8a25a4609 --- /dev/null +++ b/rs/idl-gen/src/generic_resolver.rs @@ -0,0 +1,321 @@ +use sails_idl_meta::*; +use std::collections::HashSet; + +pub(crate) fn resolve_generic_type_decl( + type_decl: &TypeDecl, + type_name: &str, + type_params: &Vec, +) -> TypeDecl { + let candidates = build_generic_candidates(type_decl, type_params); + let syn_name = syn_resolver::try_resolve(type_name).map(|td| td.to_string()); + let match_name = syn_name.unwrap_or_else(|| type_name.to_string()); + + println!( + "type_decl: {:?}, type_name: {}, match_name: {}, candidates: {:?}", + type_decl.to_string(), + type_name, + match_name, + candidates + .iter() + .map(|td| td.to_string()) + .collect::>() + ); + candidates + .into_iter() + .find(|td| td.to_string() == match_name) + .unwrap_or_else(|| panic!("Not Resolved {}", type_name)) +} + +struct GenericCandidates<'a> { + resolved: HashSet, + type_params: Vec<(&'a TypeDecl, &'a str)>, +} + +impl<'a> GenericCandidates<'a> { + fn new(type_params: &'a [sails_idl_meta::TypeParameter]) -> Self { + Self { + resolved: HashSet::new(), + type_params: type_params + .iter() + .filter_map(|tp| tp.ty.as_ref().map(|ty| (ty, tp.name.as_str()))) + .collect(), + } + } + + fn push(&mut self, candidate: TypeDecl, f: impl Fn(TypeDecl) -> TypeDecl) { + for (td, name) in &self.type_params { + if td == &&candidate { + println!( + "type_params: {:?}, candidate {:?}, td: {:?}", + &self.type_params, candidate, td + ); + self.resolved.insert(f(generic_type_decl(name))); + } + } + self.resolved.insert(f(candidate)); + } +} + +fn build_generic_candidates( + type_decl: &TypeDecl, + type_params: &Vec, +) -> HashSet { + let mut candidates = GenericCandidates::new(type_params); + // push `type_decl` as generic param to candidates + candidates.push(type_decl.clone(), |td| td); + match type_decl { + TypeDecl::Slice { item } => { + let decls = build_generic_candidates(item, type_params); + for item in decls { + candidates.push(item, |td| TypeDecl::Slice { item: Box::new(td) }); + } + } + TypeDecl::Array { item, len } => { + let decls = build_generic_candidates(item, type_params); + for item in decls { + candidates.push(item, |td| TypeDecl::Array { + item: Box::new(td), + len: *len, + }); + } + } + TypeDecl::Tuple { types } => { + for (idx, item) in types.iter().enumerate() { + let decls = build_generic_candidates(item, type_params); + let type_decls_resolved: Vec<_> = candidates + .resolved + .iter() + .filter_map(|td| match td { + TypeDecl::Tuple { types } => Some(types.clone()), + _ => None, + }) + .collect(); + for tds in type_decls_resolved { + for item in &decls { + candidates.push(item.clone(), |td| { + let mut types = tds.clone(); + types[idx] = td; + TypeDecl::Tuple { types } + }); + } + } + } + } + TypeDecl::Primitive(_) => { + // already pushed as `type_decl` + } + TypeDecl::Named { name, generics } => { + for (idx, item) in generics.iter().enumerate() { + let decls = build_generic_candidates(item, type_params); + let type_decls_resolved: Vec<_> = candidates + .resolved + .iter() + .filter_map(|td| match td { + TypeDecl::Named { + name: resolved_name, + generics, + } if resolved_name == name => Some(generics.clone()), + _ => None, + }) + .collect(); + + for tds in type_decls_resolved { + for item in &decls { + candidates.push(item.clone(), |td| { + let mut generics = tds.clone(); + generics[idx] = td; + TypeDecl::Named { + name: name.to_string(), + generics, + } + }); + } + } + } + } + }; + println!("type_decls_resolved {:?}", candidates.resolved); + candidates.resolved +} + +fn generic_type_decl(name: &str) -> TypeDecl { + TypeDecl::named(name.to_string()) +} + +mod syn_resolver { + use super::*; + use quote::ToTokens; + use syn::{ + GenericArgument, PathArguments, Type, TypeArray, TypeParen, TypePath, TypeReference, + TypeSlice, TypeTuple, + }; + + pub(super) fn try_resolve(type_name: &str) -> Option { + syn::parse_str::(type_name) + .map(|syn_type| finalize_syn(&syn_type)) + .ok() + .flatten() + } + + fn finalize_syn(t: &Type) -> Option { + use TypeDecl::*; + + match t { + Type::Array(TypeArray { elem, len, .. }) => Some(Array { + item: Box::new(finalize_syn(elem)?), + len: len.to_token_stream().to_string().parse::().unwrap(), + }), + Type::Slice(TypeSlice { elem, .. }) => Some(Slice { + item: Box::new(finalize_syn(elem)?), + }), + Type::Tuple(TypeTuple { elems, .. }) => Some(Tuple { + types: elems.iter().filter_map(finalize_syn).collect(), + }), + Type::Reference(TypeReference { elem, .. }) => finalize_syn(elem), + // No paren types in the final output. Only single value tuples + Type::Paren(TypeParen { elem, .. }) => finalize_syn(elem), + Type::Path(TypePath { path, .. }) => { + let last_segment = path.segments.last().unwrap(); + let name = last_segment.ident.to_string(); + + let generics: Vec<_> = + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + syn_args + .args + .iter() + .filter_map(finalize_type_inner) + .collect() + } else { + vec![] + }; + match name.as_str() { + "Vec" => { + if let [_] = generics.as_slice() { + Some(Slice { + item: Box::new(Tuple { types: generics }), + }) + } else { + Some(Named { name, generics }) + } + } + "BTreeMap" => { + if let [_, _] = generics.as_slice() { + Some(Slice { + item: Box::new(Tuple { types: generics }), + }) + } else { + Some(Named { name, generics }) + } + } + _ => Some(Named { name, generics }), + } + } + _ => None, + } + } + + fn finalize_type_inner(arg: &GenericArgument) -> Option { + match arg { + GenericArgument::Type(t) => finalize_syn(t), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::type_resolver::TypeResolver; + + use super::*; + use scale_info::{MetaType, PortableRegistry, Registry, TypeInfo}; + use std::collections::BTreeMap; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericStruct { + field: T, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericEnum { + Variant1(T1), + Variant2(T2), + Variant3(T1, Option), + Variant4(Option<(T1, GenericStruct, u32)>), + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + pub enum ManyVariants { + One, + Two(u32), + Three(Option>), + Four { a: u32, b: Option }, + Five(String, Vec), + Six((u32,)), + Seven(GenericEnum), + Eight([BTreeMap; 10]), + Nine(TupleVariantsDocs), + } + + #[derive(TypeInfo)] + pub enum TupleVariantsDocs { + /// Docs for no tuple docs 1 + NoTupleDocs1(u32, String), + NoTupleDocs2(gprimitives::CodeId, Vec), + /// Docs for tuple docs 1 + TupleDocs1( + u32, + /// This is the second field + String, + ), + TupleDocs2( + /// This is the first field + u32, + /// This is the second field + String, + ), + /// Docs for struct docs + StructDocs { + /// This is field `a` + a: u32, + /// This is field `b` + b: String, + }, + } + + #[test] + fn generic_resolver_struct_primitive() { + use sails_idl_meta::{PrimitiveType::*, TypeDecl::*}; + + let meta_type = MetaType::new::>(); + let mut registry = Registry::new(); + let id = registry.register_type(&meta_type).id; + let portable_registry = PortableRegistry::from(registry); + let mut resolver = TypeResolver::from_registry(&portable_registry); + let ty = portable_registry.resolve(id).unwrap(); + let type_params = resolver.resolve_type_params(ty); + + let type_decl = resolver.get(id).unwrap(); + + let candidates = build_generic_candidates(type_decl, &type_params); + println!("{:?}", candidates); + + assert_eq!(2, candidates.len()); + assert!(candidates.contains(&Named { + name: "GenericStruct".to_string(), + generics: vec![Primitive(U32)] + })); + assert!(candidates.contains(&Named { + name: "GenericStruct".to_string(), + generics: vec![Named { + name: "T".to_string(), + generics: vec![] + }] + })); + + // let string_struct = resolver.get(string_struct_id).unwrap(); + // assert_eq!(string_struct.to_string(), "GenericStruct"); + } +} diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index df057eee4..0ab60343d 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -1,28 +1,25 @@ +use askama::Template; pub use errors::*; -use handlebars::{Handlebars, handlebars_helper}; -use meta::ExpandedProgramMeta; pub use program::*; -use scale_info::{Field, PortableType, Variant, form::PortableForm}; -use serde::Serialize; +use sails_idl_meta::*; +use scale_info::{Variant, form::PortableForm}; use std::{fs, io::Write, path::Path}; +mod builder; mod errors; -mod meta; -mod type_names; +mod generic_resolver; +mod type_resolver; -const IDL_TEMPLATE: &str = include_str!("../hbs/idl.hbs"); -const COMPOSITE_TEMPLATE: &str = include_str!("../hbs/composite.hbs"); -const VARIANT_TEMPLATE: &str = include_str!("../hbs/variant.hbs"); +const SAILS_VERSION: &str = env!("CARGO_PKG_VERSION"); pub mod program { use super::*; use sails_idl_meta::ProgramMeta; - pub fn generate_idl(idl_writer: impl Write) -> Result<()> { - render_idl( - &ExpandedProgramMeta::new(Some(P::constructors()), P::services())?, - idl_writer, - ) + pub fn generate_idl(mut idl_writer: impl Write) -> Result<()> { + let doc = build_program_ast::

(Some("ProgramToDo".to_string()))?; + doc.write_into(&mut idl_writer)?; + Ok(()) } pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { @@ -44,11 +41,10 @@ pub mod service { use super::*; use sails_idl_meta::{AnyServiceMeta, ServiceMeta}; - pub fn generate_idl(idl_writer: impl Write) -> Result<()> { - render_idl( - &ExpandedProgramMeta::new(None, vec![("", AnyServiceMeta::new::())].into_iter())?, - idl_writer, - ) + pub fn generate_idl(mut idl_writer: impl Write) -> Result<()> { + let doc = build_service_ast("ServiceToDo", AnyServiceMeta::new::())?; + doc.write_into(&mut idl_writer)?; + Ok(()) } pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { @@ -63,58 +59,35 @@ pub mod service { } } -fn render_idl(program_meta: &ExpandedProgramMeta, idl_writer: impl Write) -> Result<()> { - let program_idl_data = ProgramIdlData { - type_names: program_meta.type_names()?.collect(), - types: program_meta.types().collect(), - ctors: program_meta.ctors().collect(), - services: program_meta - .services() - .map(|s| ServiceIdlData { - name: s.name(), - commands: s.commands().collect(), - queries: s.queries().collect(), - events: s.events().collect(), - }) - .collect(), +fn build_program_ast(name: Option) -> Result { + // let + let service_builders: Vec<_> = P::services() + .map(|(name, meta)| builder::ServiceBuilder::new(name, meta)) + .collect(); + let services: Vec<_> = service_builders.into_iter().map(|b| b.build()).collect(); + let program = name.map(|name| builder::ProgramBuilder::new::

().build(name)); + let doc = IdlDoc { + globals: vec![ + ("sails".to_string(), Some(SAILS_VERSION.to_string())), + // ("author".to_string(), Some(gen_meta_info.author)), + // ("version".to_string(), Some(gen_meta_info.version.format())), + ], + program, + services, }; - - let mut handlebars = Handlebars::new(); - handlebars - .register_template_string("idl", IDL_TEMPLATE) - .map_err(Box::new)?; - handlebars - .register_template_string("composite", COMPOSITE_TEMPLATE) - .map_err(Box::new)?; - handlebars - .register_template_string("variant", VARIANT_TEMPLATE) - .map_err(Box::new)?; - handlebars.register_helper("deref", Box::new(deref)); - - handlebars - .render_to_write("idl", &program_idl_data, idl_writer) - .map_err(Box::new)?; - - Ok(()) -} - -type CtorIdlData<'a> = (&'a str, &'a Vec>, &'a Vec); -type FuncIdlData<'a> = (&'a str, &'a Vec>, u32, &'a Vec); - -#[derive(Serialize)] -struct ProgramIdlData<'a> { - type_names: Vec, - types: Vec<&'a PortableType>, - ctors: Vec>, - services: Vec>, + Ok(doc) } -#[derive(Serialize)] -struct ServiceIdlData<'a> { - name: &'a str, - commands: Vec>, - queries: Vec>, - events: Vec<&'a Variant>, +fn build_service_ast(name: &'static str, meta: AnyServiceMeta) -> Result { + let services: Vec<_> = vec![builder::ServiceBuilder::new(name, meta).build()]; + let doc = IdlDoc { + globals: vec![ + ("sails".to_string(), Some(SAILS_VERSION.to_string())), + // ("author".to_string(), Some(gen_meta_info.author)), + // ("version".to_string(), Some(gen_meta_info.version.format())), + ], + program: None, + services, + }; + Ok(doc) } - -handlebars_helper!(deref: |v: String| { v }); diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs new file mode 100644 index 000000000..47495e17e --- /dev/null +++ b/rs/idl-gen/src/type_resolver.rs @@ -0,0 +1,615 @@ +use convert_case::{Case, Casing}; +use sails_idl_meta::*; +use scale_info::{ + Field, Path, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, + TypeDefPrimitive, TypeDefVariant, form::PortableForm, +}; +use std::collections::{BTreeMap, HashMap, HashSet}; + +#[derive(Debug, Clone)] +pub struct TypeResolver<'a> { + registry: &'a PortableRegistry, + exclude: HashSet, + map: HashMap, + user_defined: HashMap, +} + +#[derive(Debug, Clone)] +pub struct UserDefinedEntry { + pub meta_type: sails_idl_meta::Type, + pub path: Path, +} + +impl UserDefinedEntry { + fn is_type(&self, type_info: &Type) -> bool { + self.path == type_info.path + } +} + +impl<'a> TypeResolver<'a> { + pub fn from_registry(registry: &'a PortableRegistry) -> Self { + let mut resolver = Self { + registry, + exclude: HashSet::new(), + map: HashMap::new(), + user_defined: HashMap::new(), + }; + resolver.build_type_decl_map(); + resolver + } + + pub fn from(registry: &'a PortableRegistry, exclude: HashSet) -> Self { + let mut resolver = Self { + registry, + exclude, + map: HashMap::new(), + user_defined: HashMap::new(), + }; + resolver.build_type_decl_map(); + resolver + } + + pub fn into_types(self) -> Vec { + let mut vec: Vec<_> = self + .user_defined + .into_values() + .map(|v| v.meta_type) + .collect(); + vec.sort_by(|a, b| a.name.cmp(&b.name)); + vec + } + + pub fn get(&self, key: u32) -> Option<&TypeDecl> { + self.map.get(&key) + } + + fn build_type_decl_map(&mut self) { + let filtered: Vec<_> = self + .registry + .types + .iter() + .filter(|pt| !self.exclude.contains(&pt.id)) + .collect(); + for pt in filtered { + let type_decl = self.resolve_type_decl(&pt.ty); + self.map.insert(pt.id, type_decl); + } + } + + fn resolve_by_id(&mut self, id: u32) -> TypeDecl { + if let Some(decl) = self.get(id) { + return decl.clone(); + } + let ty = self.registry.resolve(id).unwrap(); + let type_decl = self.resolve_type_decl(ty); + self.map.insert(id, type_decl.clone()); + type_decl + } + + fn resolve_type_decl(&mut self, ty: &Type) -> TypeDecl { + match &ty.type_def { + TypeDef::Composite(type_def_composite) => self + .resolve_known_composite(ty, type_def_composite) + .unwrap_or_else(|| { + let name = self.register_user_defined(ty); + self.resolve_user_defined(name, ty) + }), + TypeDef::Variant(type_def_variant) => self + .resolve_known_enum(ty, type_def_variant) + .unwrap_or_else(|| { + let name = self.register_user_defined(ty); + self.resolve_user_defined(name, ty) + }), + TypeDef::Sequence(type_def_sequence) => TypeDecl::Slice { + item: Box::new(self.resolve_by_id(type_def_sequence.type_param.id)), + }, + TypeDef::Array(type_def_array) => TypeDecl::Array { + item: Box::new(self.resolve_by_id(type_def_array.type_param.id)), + len: type_def_array.len, + }, + TypeDef::Tuple(type_def_tuple) => { + if type_def_tuple.fields.is_empty() { + TypeDecl::Primitive(PrimitiveType::Void) + } else { + TypeDecl::tuple( + type_def_tuple + .fields + .iter() + .map(|f| self.resolve_by_id(f.id)) + .collect(), + ) + } + } + TypeDef::Primitive(type_def_primitive) => { + TypeDecl::Primitive(primitive_map(&type_def_primitive)) + } + TypeDef::Compact(_) => unimplemented!("TypeDef::Compact is unimplemented"), + TypeDef::BitSequence(_) => { + unimplemented!("TypeDef::BitSequence is unimplemented") + } + } + } + + fn register_user_defined(&mut self, ty: &Type) -> String { + let name = match self.unique_type_name(ty) { + Ok(name) => name, + Err(exist) => return exist, + }; + + let type_params = self.resolve_type_params(ty); + + let def = match &ty.type_def { + TypeDef::Composite(type_def_composite) => { + let fields = type_def_composite + .fields + .iter() + .map(|f| self.resolve_field(f, &type_params)) + .collect(); + sails_idl_meta::TypeDef::Struct(StructDef { fields }) + } + TypeDef::Variant(type_def_variant) => { + let variants = type_def_variant + .variants + .iter() + .map(|v| { + let fields = v + .fields + .iter() + .map(|f| self.resolve_field(f, &type_params)) + .collect(); + EnumVariant { + name: v.name.to_string(), + def: StructDef { fields }, + docs: v.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], // ("index".to_string(), Some(v.index.to_string())) + } + }) + .collect(); + + sails_idl_meta::TypeDef::Enum(EnumDef { variants }) + } + _ => unreachable!(), + }; + + let meta_type = sails_idl_meta::Type { + name: name.clone(), + type_params, + def, + docs: ty.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], //("rust_type".to_string(), Some(ty.path.to_string())) + }; + let path = ty.path.clone(); + self.user_defined + .insert(name.clone(), UserDefinedEntry { meta_type, path }); + name + } + + pub(crate) fn resolve_type_params( + &mut self, + ty: &Type, + ) -> Vec { + ty.type_params + .iter() + .map(|tp| sails_idl_meta::TypeParameter { + name: tp.name.to_string(), + ty: tp.ty.map(|ty| self.resolve_by_id(ty.id)), + }) + .collect() + } + + fn unique_type_name(&self, ty: &Type) -> Result { + for name in possible_names_by_path(ty) { + if let Some(exists) = self.user_defined.get(&name) { + if exists.is_type(ty) { + // type already registered + return Err(name); + } else { + continue; + } + } + return Ok(name); + } + unreachable!(); + } + + fn resolve_user_defined(&mut self, name: String, ty: &Type) -> TypeDecl { + let generics = ty + .type_params + .iter() + .map(|tp| self.resolve_by_id(tp.ty.as_ref().unwrap().id)) + .collect(); + TypeDecl::Named { name, generics } + } + + fn resolve_field( + &mut self, + field: &Field, + type_params: &Vec, + ) -> StructField { + let resolved = self.resolve_by_id(field.ty.id); + let type_decl = if let Some(type_name) = field.type_name.as_ref() + && &resolved.to_string() != type_name + && !type_params.is_empty() + { + crate::generic_resolver::resolve_generic_type_decl(&resolved, type_name, type_params) + } else { + resolved + }; + StructField { + name: field.name.as_ref().map(|s| s.to_string()), + type_decl, + docs: field.docs.iter().map(|d| d.to_string()).collect(), + annotations: vec![], + } + } + + fn resolve_known_composite( + &mut self, + ty: &Type, + _def: &TypeDefComposite, + ) -> Option { + use PrimitiveType::*; + use TypeDecl::*; + + if is_type::(ty) { + Some(Primitive(H160)) + } else if is_type::(ty) { + Some(Primitive(H256)) + } else if is_type::(ty) { + Some(Primitive(U256)) + } else if is_type::(ty) { + Some(Primitive(ActorId)) + } else if is_type::(ty) { + Some(Primitive(CodeId)) + } else if is_type::(ty) { + Some(Primitive(MessageId)) + } else if is_type::>(ty) + && let [vec_tp] = ty.type_params.as_slice() + && let Some(ty) = vec_tp.ty + { + let ty = self.resolve_by_id(ty.id); + Some(Slice { item: Box::new(ty) }) + } else if is_type::>(ty) + && let [key_tp, value_tp] = ty.type_params.as_slice() + && let Some(key) = key_tp.ty + && let Some(value) = value_tp.ty + { + let key = self.resolve_by_id(key.id); + let value = self.resolve_by_id(value.id); + Some(Slice { + item: Box::new(TypeDecl::tuple(vec![key, value])), + }) + } else { + None + } + } + + fn resolve_known_enum( + &mut self, + ty: &Type, + def: &TypeDefVariant, + ) -> Option { + if is_type::>(ty) + && let [ok_var, err_var] = def.variants.as_slice() + && let [ok] = ok_var.fields.as_slice() + && let [err] = err_var.fields.as_slice() + { + let ok = self.resolve_by_id(ok.ty.id); + let err = self.resolve_by_id(err.ty.id); + Some(TypeDecl::result(ok, err)) + } else if is_type::>(ty) + && let [_, some_var] = def.variants.as_slice() + && let [some] = some_var.fields.as_slice() + { + let decl = self.resolve_by_id(some.ty.id); + Some(TypeDecl::option(decl)) + } else { + None + } + } +} + +fn is_type(type_info: &Type) -> bool { + T::type_info().path.segments == type_info.path.segments +} + +fn primitive_map(type_def_primitive: &TypeDefPrimitive) -> PrimitiveType { + use PrimitiveType::*; + + match type_def_primitive { + TypeDefPrimitive::Bool => Bool, + TypeDefPrimitive::Char => Char, + TypeDefPrimitive::Str => String, + TypeDefPrimitive::U8 => U8, + TypeDefPrimitive::U16 => U16, + TypeDefPrimitive::U32 => U32, + TypeDefPrimitive::U64 => U64, + TypeDefPrimitive::U128 => U128, + TypeDefPrimitive::U256 => U256, + TypeDefPrimitive::I8 => I8, + TypeDefPrimitive::I16 => I16, + TypeDefPrimitive::I32 => I32, + TypeDefPrimitive::I64 => I64, + TypeDefPrimitive::I128 => I128, + TypeDefPrimitive::I256 => todo!(), + } +} + +fn possible_names_by_path(ty: &Type) -> impl Iterator + '_ { + let mut name = String::default(); + ty.path.segments.iter().rev().map(move |segment| { + name = segment.to_case(Case::Pascal) + &name; + name.clone() + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use scale_info::{MetaType, Registry, TypeInfo}; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericStruct { + field: T, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericConstStruct { + field: [T; N], + field2: [T; M], + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericEnum { + Variant1(T1), + Variant2(T2), + Variant3(T1, Option), + Variant4(Option<(T1, GenericStruct, u32)>), + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + pub enum ManyVariants { + One, + Two(u32), + Three(Option>), + Four { a: u32, b: Option }, + Five(String, Vec), + Six((u32,)), + Seven(GenericEnum), + Eight([BTreeMap; 10]), + Nine(TupleVariantsDocs), + } + + #[derive(TypeInfo)] + pub enum TupleVariantsDocs { + /// Docs for no tuple docs 1 + NoTupleDocs1(u32, String), + NoTupleDocs2(gprimitives::CodeId, Vec), + /// Docs for tuple docs 1 + TupleDocs1( + u32, + /// This is the second field + String, + ), + TupleDocs2( + /// This is the first field + u32, + /// This is the second field + String, + ), + /// Docs for struct docs + StructDocs { + /// This is field `a` + a: u32, + /// This is field `b` + b: String, + }, + } + + #[test] + fn type_resolver_h160_h256() { + let mut registry = Registry::new(); + let _h160_id = registry + .register_type(&MetaType::new::()) + .id; + let _h160_as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + + let h256_id = registry + .register_type(&MetaType::new::()) + .id; + let h256_as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + + let resolver = TypeResolver::from_registry(&portable_registry); + println!("{:#?}", resolver); + + let h256_decl = resolver.get(h256_id).unwrap(); + assert_eq!(*h256_decl, TypeDecl::Primitive(PrimitiveType::H256)); + + let generic_struct_decl = resolver.get(h256_as_generic_param_id).unwrap(); + assert_eq!( + *generic_struct_decl, + TypeDecl::Named { + name: "GenericStruct".to_string(), + generics: vec![TypeDecl::Primitive(PrimitiveType::H256)] + } + ); + assert_eq!(generic_struct_decl.to_string(), "GenericStruct"); + } + + #[test] + fn type_resolver_generic_struct() { + let mut registry = Registry::new(); + let u32_struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let string_struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + println!("{:#?}", resolver); + + let u32_struct = resolver.get(u32_struct_id).unwrap(); + assert_eq!(u32_struct.to_string(), "GenericStruct"); + + let string_struct = resolver.get(string_struct_id).unwrap(); + assert_eq!(string_struct.to_string(), "GenericStruct"); + } + + #[test] + fn type_resolver_generic_enum() { + let mut registry = Registry::new(); + let u32_string_enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let bool_u32_enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + println!("{:#?}", resolver); + + let u32_string_enum = resolver.get(u32_string_enum_id).unwrap(); + assert_eq!(u32_string_enum.to_string(), "GenericEnum"); + + let bool_u32_enum = resolver.get(bool_u32_enum_id).unwrap(); + assert_eq!(bool_u32_enum.to_string(), "GenericEnum"); + } + + #[test] + fn type_resolver_array_type() { + let mut registry = Registry::new(); + let u32_array_id = registry.register_type(&MetaType::new::<[u32; 10]>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_array = resolver.get(u32_array_id).unwrap(); + assert_eq!(u32_array.to_string(), "[u32; 10]"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct<[u32; 10]>"); + } + + #[test] + fn type_resolver_vector_type() { + let mut registry = Registry::new(); + let u32_vector_id = registry.register_type(&MetaType::new::>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_vector = resolver.get(u32_vector_id).unwrap(); + assert_eq!(u32_vector.to_string(), "[u32]"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct<[u32]>"); + } + + #[test] + fn type_resolver_result_type() { + let mut registry = Registry::new(); + let u32_result_id = registry + .register_type(&MetaType::new::>()) + .id; + let as_generic_param_id = registry + .register_type(&MetaType::new::< + GenericStruct>, + >()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_result = resolver.get(u32_result_id).unwrap(); + assert_eq!(u32_result.to_string(), "Result"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + "GenericStruct>" + ); + } + + #[test] + fn type_resolver_option_type() { + let mut registry = Registry::new(); + let u32_option_id = registry.register_type(&MetaType::new::>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + println!("{:#?}", resolver); + + let u32_option = resolver.get(u32_option_id).unwrap(); + assert_eq!(u32_option.to_string(), "Option"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct>"); + } + + #[test] + fn type_resolver_tuple_type() { + let mut registry = Registry::new(); + let u32_str_tuple_id = registry.register_type(&MetaType::new::<(u32, String)>()).id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let u32_str_tuple = resolver.get(u32_str_tuple_id).unwrap(); + assert_eq!(u32_str_tuple.to_string(), "(u32, String)"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct<(u32, String)>"); + } + + #[test] + fn type_resolver_btree_map_type() { + let mut registry = Registry::new(); + let btree_map_id = registry + .register_type(&MetaType::new::>()) + .id; + let as_generic_param_id = registry + .register_type(&MetaType::new::>>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + println!("{:#?}", resolver); + + let btree_map = resolver.get(btree_map_id).unwrap(); + assert_eq!(btree_map.to_string(), "[(u32, String)]"); + let as_generic_param = resolver.get(as_generic_param_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + "GenericStruct<[(u32, String)]>" + ); + } + + #[test] + fn type_resolver_enum_many_variants() { + let mut registry = Registry::new(); + let id = registry.register_type(&MetaType::new::()).id; + let generic_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + // println!("{:#?}", resolver); + + let ty = resolver.get(id).unwrap(); + assert_eq!(ty.to_string(), "ManyVariants"); + let as_generic_param = resolver.get(generic_id).unwrap(); + assert_eq!(as_generic_param.to_string(), "GenericStruct"); + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap index 040a159b1..c1b0cd014 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap @@ -2,106 +2,89 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; +service { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStruct { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +program ProgramToDo { + services { + , + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap index a28c014af..b9048f1ec 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap @@ -2,136 +2,169 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; - -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +service { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStruct { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} service SomeService { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStruct { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +program ProgramToDo { + services { + , + SomeService, + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap index fc0fe672e..2a06a5179 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap @@ -2,114 +2,96 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; +service { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStruct { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -constructor { - /// This is New constructor - New : (); - /// This is FromStr constructor - /// with second line - FromStr : (p1: str); -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +program ProgramToDo { + constructors { + /// This is New constructor + New(); + /// This is FromStr constructor + /// with second line + FromStr(p1: String); + } + services { + , + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap index db66155ff..223cb0972 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -2,112 +2,83 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; - -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - DoThatBase : (p1: str) -> str; - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - query ThisBase : (p1: u16) -> u16; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - ThisDoneBase: u32; - ThatDoneBase: struct { - p1: u16 - }; - } -}; +service ServiceToDo { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStruct { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap index 040a159b1..223cb0972 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap @@ -2,106 +2,83 @@ source: rs/idl-gen/tests/generator.rs expression: generated_idl --- -/// TupleStruct docs -type TupleStruct = struct { - bool, -}; +!@sails: 0.9.2 -/// GenericStruct docs -type GenericStructForH256 = struct { - /// GenericStruct field `p1` - p1: h256, -}; - -/// GenericStruct docs -type GenericStructForStr = struct { - /// GenericStruct field `p1` - p1: str, -}; - -/// GenericConstStruct docs -type GenericConstStruct1 = struct { - /// GenericStruct field `field` - field: [u8, 8], -}; - -/// GenericConstStruct docs -type GenericConstStruct2 = struct { - /// GenericStruct field `field` - field: [u8, 32], -}; - -type DoThatParam = struct { - p1: u32, - p2: str, - p3: ManyVariants, -}; - -type ManyVariants = enum { - One, - Two: u32, - Three: opt vec u256, - Four: struct { - a: u32, - b: opt u16, - }, - Five: struct { - str, - vec u8, - }, - Six: struct { u32 }, - Seven: GenericEnumForU32AndStr, - Eight: [map (u32, str), 10], -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForU32AndStr = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: u32, - /// GenericEnum `Variant2` of type 'T2' - Variant2: str, -}; - -/// GenericEnum docs -/// with two lines -type GenericEnumForBoolAndU32 = enum { - /// GenericEnum `Variant1` of type 'T1' - Variant1: bool, - /// GenericEnum `Variant2` of type 'T2' - Variant2: u32, -}; - -type ThatParam = struct { - p1: ManyVariants, -}; - -service { - /// Some description - DoThis : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericStructForH256, p6: GenericStructForStr, p7: GenericConstStruct1, p8: GenericConstStruct2) -> str; - /// Some multiline description - /// Second line - /// Third line - DoThat : (par1: DoThatParam) -> result (struct { str, u32 }, struct { str }); - /// This is a query - query This : (p1: u32, p2: str, p3: struct { opt str, u8 }, p4: TupleStruct, p5: GenericEnumForBoolAndU32) -> result (struct { str, u32 }, str); - /// This is a second query - /// This is a second line - query That : (pr1: ThatParam) -> str; - - events { - /// `This` Done - ThisDone: u32; - ThisDoneTwice: struct { - /// This is the first unnamed field - u32, - /// This is the second unnamed field - u32, - }; - /// `That` Done too - ThatDone: struct { - /// This is `p1` field - p1: str - }; - } -}; +service ServiceToDo { + events { + /// `This` Done + ThisDone ( + /// This is unnamed field, comments ignored + u32, + ), + ThisDoneTwice ( + /// This is the first unnamed field + u32, + /// This is the second unnamed field + u32, + ), + /// `That` Done too + ThatDone { + /// This is `p1` field + p1: String, + }, + } + functions { + /// Some description + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + /// Some multiline description + /// Second line + /// Third line + DoThat(par1: DoThatParam) -> (String, u32) throws (String); + /// This is a query + @query + This(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericEnum) -> (String, u32) throws String; + /// This is a second query + /// This is a second line + @query + That(pr1: ThatParam) -> String; + } + types { + struct DoThatParam { + p1: u32, + p2: String, + p3: ManyVariants, + } + /// GenericConstStruct docs + struct GenericConstStruct { + /// GenericStruct field `field` + field: [u8; 8], + } + /// GenericEnum docs + /// with two lines + enum GenericEnum { + /// GenericEnum `Variant1` of type 'T1' + Variant1(T1), + /// GenericEnum `Variant2` of type 'T2' + Variant2(T2), + } + /// GenericStruct docs + struct GenericStruct { + /// GenericStruct field `p1` + p1: T, + } + enum ManyVariants { + One, + Two(u32), + Three(Option<[U256]>), + Four { + a: u32, + b: Option, + }, + Five(String, [u8]), + Six((u32)), + Seven(GenericEnum), + Eight([[(u32, String)]; 10]), + } + struct ThatParam { + p1: ManyVariants, + } + /// TupleStruct docs + struct TupleStruct(bool); + } +} From 38b134725a10b9b101f04e7aecd4dea6a5589cc2 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 2 Dec 2025 13:28:25 +0100 Subject: [PATCH 02/16] wip: idl gen --- Cargo.lock | 1 - benchmarks/idls/alloc_stress_program.idl | 23 +- benchmarks/ping-pong/client/ping_pong.idl | 23 +- examples/demo/client/demo_client.idl | 150 +++++------ examples/no-svcs-prog/wasm/no_svcs_prog.idl | 7 +- examples/redirect/client/redirect_client.idl | 23 +- examples/rmrk/catalog/wasm/rmrk-catalog.idl | 54 ++-- rs/idl-gen/Cargo.toml | 1 - rs/idl-gen/src/builder.rs | 255 ++++++++++-------- rs/idl-gen/src/errors.rs | 10 +- rs/idl-gen/src/generic_resolver.rs | 4 +- rs/idl-gen/src/lib.rs | 19 +- rs/idl-gen/src/type_resolver.rs | 30 +-- rs/idl-gen/tests/generator.rs | 92 +++---- ...r__program_idl_works_with_empty_ctors.snap | 4 +- ...gram_idl_works_with_multiple_services.snap | 4 +- 16 files changed, 356 insertions(+), 344 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d22a19b6..57f87c9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7231,7 +7231,6 @@ dependencies = [ "askama", "convert_case 0.7.1", "gprimitives", - "handlebars", "insta", "quote", "sails-idl-meta", diff --git a/benchmarks/idls/alloc_stress_program.idl b/benchmarks/idls/alloc_stress_program.idl index 298ab3b02..208c0ce93 100644 --- a/benchmarks/idls/alloc_stress_program.idl +++ b/benchmarks/idls/alloc_stress_program.idl @@ -1,9 +1,9 @@ -program AllocStress { - constructors { - NewForBench(); - } - services { - AllocStress, + +!@sails: 0.9.2 + +service AllocStress { + functions { + AllocStress(n: u32) -> AllocStressResult; } types { struct AllocStressResult { @@ -12,8 +12,11 @@ program AllocStress { } } -service AllocStress { - functions { - AllocStress(n: u32) -> AllocStressResult; +program ProgramToDo { + constructors { + NewForBench(); } -} \ No newline at end of file + services { + AllocStress, + } +} diff --git a/benchmarks/ping-pong/client/ping_pong.idl b/benchmarks/ping-pong/client/ping_pong.idl index 2d6d12f2f..67fbde397 100644 --- a/benchmarks/ping-pong/client/ping_pong.idl +++ b/benchmarks/ping-pong/client/ping_pong.idl @@ -1,9 +1,9 @@ -program PingPongProgram { - constructors { - NewForBench(); - } - services { - PingPongService, + +!@sails: 0.9.2 + +service PingPongService { + functions { + Ping(payload: PingPongPayload) -> PingPongPayload; } types { enum PingPongPayload { @@ -15,8 +15,11 @@ program PingPongProgram { } } -service PingPongService { - functions { - Ping(payload: PingPongPayload) -> PingPongPayload; +program ProgramToDo { + constructors { + NewForBench(); } -} \ No newline at end of file + services { + PingPongService, + } +} diff --git a/examples/demo/client/demo_client.idl b/examples/demo/client/demo_client.idl index 6db63cc4d..2cbb64d22 100644 --- a/examples/demo/client/demo_client.idl +++ b/examples/demo/client/demo_client.idl @@ -1,57 +1,19 @@ -program Demo { - constructors { - /// Program constructor (called once at the very beginning of the program lifetime) - Default(); - /// Another program constructor (called once at the very beginning of the program lifetime) - New(counter: Option, dog_position: Option<(i32, i32)>); - } - services { - PingPong, - Counter, - Dog, - References, - ThisThat, - ValueFee, - Chaos, - } - types { - struct ReferenceCount(u32); - - struct DoThatParam { - p1: NonZeroU32, - p2: ActorId, - p3: ManyVariants, - } - - enum ManyVariants { - One, - Two(u32), - Three(Option), - Four { a: u32, b: Option }, - Five(string, H256), - Six(u32), - } - - enum ManyVariantsReply { - One, - Two, - Three, - Four, - Five, - Six, - } - struct TupleStruct(bool); - } -} +!@sails: 0.9.2 service PingPong { functions { - Ping(input: string) -> Result; + Ping(input: String) -> String throws String; } } service Counter { + events { + /// Emitted when a new value is added to the counter + Added(u32), + /// Emitted when a value is subtracted from the counter + Subtracted(u32), + } functions { /// Add a value to the counter Add(value: u32) -> u32; @@ -61,26 +23,14 @@ service Counter { @query Value() -> u32; } - events { - /// Emitted when a new value is added to the counter - Added(u32), - /// Emitted when a value is subtracted from the counter - Subtracted(u32), - } } service Dog { - functions { - MakeSound() -> string; - Walk(dx: i32, dy: i32) -> (); - @query - AvgWeight() -> u32; - @query - Position() -> (i32, i32); - } events { Barked, - Walked { from: (i32, i32), to: (i32, i32) }, + } + functions { + MakeSound() -> String; } } @@ -88,48 +38,98 @@ service References { functions { Add(v: u32) -> u32; AddByte(byte: u8) -> [u8]; - GuessNum(number: u8) -> Result; + GuessNum(number: u8) -> String throws String; Incr() -> ReferenceCount; - SetNum(number: u8) -> Result<(), string>; + SetNum(number: u8) throws String; @query - Baked() -> string; + Baked() -> String; @query LastByte() -> Option; @query - Message() -> Option; + Message() -> Option; + } + types { + struct ReferenceCount(u32); } } service ThisThat { functions { - DoThat(param: DoThatParam) -> Result<(ActorId, NonZeroU32, ManyVariantsReply), (string,)>; - DoThis(p1: u32, p2: string, p3: (Option, NonZeroU8), p4: TupleStruct) -> (string, u32); - Noop() -> (); + DoThat(param: DoThatParam) -> (ActorId, NonZeroU32, ManyVariantsReply) throws (String); + DoThis(p1: u32, p2: String, p3: (Option, NonZeroU8), p4: TupleStruct) -> (String, u32); + Noop(); @query - That() -> Result; + That() -> String throws String; @query This() -> u32; } + types { + struct DoThatParam { + p1: NonZeroU32, + p2: ActorId, + p3: ManyVariants, + } + enum ManyVariants { + One, + Two(u32), + Three(Option), + Four { + a: u32, + b: Option, + }, + Five(String, H256), + Six((u32)), + } + enum ManyVariantsReply { + One, + Two, + Three, + Four, + Five, + Six, + } + struct NonZeroU32(u32); + struct NonZeroU8(u8); + struct TupleStruct(bool); + } } service ValueFee { + events { + Withheld(u128), + } functions { /// Return flag if fee taken and remain value, /// using special type `CommandReply` DoSomethingAndTakeFee() -> bool; } - events { - Withheld(u128), - } } service Chaos { functions { @query - PanicAfterWait() -> (); + PanicAfterWait(); @query ReplyHookCounter() -> u32; @query - TimeoutWait() -> (); + TimeoutWait(); + } +} + +program ProgramToDo { + constructors { + /// Program constructor (called once at the very beginning of the program lifetime) + Default(); + /// Another program constructor (called once at the very beginning of the program lifetime) + New(counter: Option, dog_position: Option<(i32, i32)>); } -} \ No newline at end of file + services { + PingPong, + Counter, + Dog, + References, + ThisThat, + ValueFee, + Chaos, + } +} diff --git a/examples/no-svcs-prog/wasm/no_svcs_prog.idl b/examples/no-svcs-prog/wasm/no_svcs_prog.idl index a72282022..443f67a40 100644 --- a/examples/no-svcs-prog/wasm/no_svcs_prog.idl +++ b/examples/no-svcs-prog/wasm/no_svcs_prog.idl @@ -1,5 +1,8 @@ -program NoSvcsProg { + +!@sails: 0.9.2 + +program ProgramToDo { constructors { Create(); } -} \ No newline at end of file +} diff --git a/examples/redirect/client/redirect_client.idl b/examples/redirect/client/redirect_client.idl index 43361dcfc..d1769e7d7 100644 --- a/examples/redirect/client/redirect_client.idl +++ b/examples/redirect/client/redirect_client.idl @@ -1,18 +1,21 @@ -program Redirect { - constructors { - New(); - } - services { - Redirect, - } -} + +!@sails: 0.9.2 service Redirect { functions { /// Exit from program with inheritor ID - Exit(inheritor_id: ActorId) -> (); + Exit(inheritor_id: ActorId); /// Returns program ID of the current program @query GetProgramId() -> ActorId; } -} \ No newline at end of file +} + +program ProgramToDo { + constructors { + New(); + } + services { + Redirect, + } +} diff --git a/examples/rmrk/catalog/wasm/rmrk-catalog.idl b/examples/rmrk/catalog/wasm/rmrk-catalog.idl index b77d7d1e0..de4ca9807 100644 --- a/examples/rmrk/catalog/wasm/rmrk-catalog.idl +++ b/examples/rmrk/catalog/wasm/rmrk-catalog.idl @@ -1,9 +1,18 @@ -program RmrkCatalog { - constructors { - New(); - } - services { - RmrkCatalog, + +!@sails: 0.9.2 + +service RmrkCatalog { + functions { + AddEquippables(part_id: u32, collection_ids: [ActorId]) -> (u32, [ActorId]) throws Error; + AddParts(parts: [(u32, Part)]) -> [(u32, Part)] throws Error; + RemoveEquippable(part_id: u32, collection_id: ActorId) -> (u32, ActorId) throws Error; + RemoveParts(part_ids: [u32]) -> [u32] throws Error; + ResetEquippables(part_id: u32) throws Error; + SetEquippablesToAll(part_id: u32) throws Error; + @query + Equippable(part_id: u32, collection_id: ActorId) -> bool throws Error; + @query + Part(part_id: u32) -> Option; } types { enum Error { @@ -15,21 +24,18 @@ program RmrkCatalog { WrongPartFormat, NotAllowedToCall, } - - enum Part { - Fixed(FixedPart), - Slot(SlotPart), - } - struct FixedPart { /// An optional zIndex of base part layer. /// specifies the stack order of an element. /// An element with greater stack order is always in front of an element with a lower stack order. z: Option, /// The metadata URI of the part. - metadata_uri: string, + metadata_uri: String, + } + enum Part { + Fixed(FixedPart), + Slot(SlotPart), } - struct SlotPart { /// Array of whitelisted collections that can be equipped in the given slot. Used with slot parts only. equippable: [ActorId], @@ -38,22 +44,16 @@ program RmrkCatalog { /// An element with greater stack order is always in front of an element with a lower stack order. z: Option, /// The metadata URI of the part. - metadata_uri: string, + metadata_uri: String, } } } -service RmrkCatalog { - functions { - AddEquippables(part_id: u32, collection_ids: [ActorId]) -> Result<(u32, [ActorId]), Error>; - AddParts(parts: [(u32, Part)]) -> Result<[(u32, Part)], Error>; - RemoveEquippable(part_id: u32, collection_id: ActorId) -> Result<(u32, ActorId), Error>; - RemoveParts(part_ids: [u32]) -> Result<[u32], Error>; - ResetEquippables(part_id: u32) -> Result<(), Error>; - SetEquippablesToAll(part_id: u32) -> Result<(), Error>; - @query - Equippable(part_id: u32, collection_id: ActorId) -> Result; - @query - Part(part_id: u32) -> Option; +program ProgramToDo { + constructors { + New(); + } + services { + RmrkCatalog, } } diff --git a/rs/idl-gen/Cargo.toml b/rs/idl-gen/Cargo.toml index cb33971d4..daa120641 100644 --- a/rs/idl-gen/Cargo.toml +++ b/rs/idl-gen/Cargo.toml @@ -13,7 +13,6 @@ rust-version.workspace = true askama.workspace = true convert_case.workspace = true gprimitives.workspace = true -handlebars.workspace = true sails-idl-meta = { workspace = true, features = ["ast", "templates"] } scale-info = { workspace = true, features = ["derive", "docs", "serde"] } serde = { workspace = true, features = ["derive"] } diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index f3709b8c8..f4d90de9d 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; pub struct ProgramBuilder { registry: PortableRegistry, - ctors_type_id: u32, // ctor_fns: Vec, + ctors_type_id: u32, services_expo: Vec, } @@ -33,24 +33,37 @@ impl ProgramBuilder { fn ctor_funcs(&self, resolver: &TypeResolver) -> Result> { any_funcs(&self.registry, self.ctors_type_id)? .map(|c| { - assert_eq!(1, c.fields.len()); - let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); - if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { - Ok(CtorFunc { - name: c.name.to_string(), - params: params_type - .fields - .iter() - .map(|f| FuncParam { - name: f.name.unwrap().to_string(), - type_decl: resolver.get(f.ty.id).unwrap().clone(), - }) - .collect(), - docs: c.docs.iter().map(|s| s.to_string()).collect(), - annotations: vec![], - }) + if c.fields.len() != 1 { + Err(Error::FuncMetaIsInvalid(format!( + "ctor `{}` has invalid number of fields", + c.name + ))) } else { - unreachable!() + let params_type_id = c.fields[0].ty.id; + let params_type = &self + .registry + .resolve(params_type_id) + .ok_or(Error::TypeIdIsUnknown(params_type_id))?; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + Ok(CtorFunc { + name: c.name.to_string(), + params: params_type + .fields + .iter() + .map(|f| FuncParam { + name: f.name.as_ref().unwrap().to_string(), + type_decl: resolver.get(f.ty.id).unwrap().clone(), + }) + .collect(), + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![], + }) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "ctor `{}` params type is not a composite", + c.name + ))) + } } }) .collect() @@ -60,23 +73,23 @@ impl ProgramBuilder { &self.services_expo } - pub fn build(self, name: String) -> ProgramUnit { + pub fn build(self, name: String) -> Result { let mut exclude = HashSet::new(); exclude.insert(self.ctors_type_id); - exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)); + exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)?); let resolver = TypeResolver::from(&self.registry, exclude); - let ctors = self.ctor_funcs(&resolver).unwrap(); + let ctors = self.ctor_funcs(&resolver)?; let services = self.services_expo().clone(); let types = resolver.into_types(); - ProgramUnit { + Ok(ProgramUnit { name, ctors, services, types, docs: vec![], annotations: vec![], - } + }) } } @@ -84,9 +97,9 @@ fn any_funcs( registry: &PortableRegistry, func_type_id: u32, ) -> Result>> { - let funcs = registry.resolve(func_type_id).unwrap_or_else(|| { - panic!("func type id {func_type_id} not found while it was registered previously") - }); + let funcs = registry + .resolve(func_type_id) + .ok_or(Error::TypeIdIsUnknown(func_type_id))?; if let scale_info::TypeDef::Variant(variant) = &funcs.type_def { Ok(variant.variants.iter()) } else { @@ -96,24 +109,12 @@ fn any_funcs( } } -fn any_funcs_ids(registry: &PortableRegistry, func_type_id: u32) -> impl Iterator { - let funcs = registry.resolve(func_type_id).unwrap(); - if let scale_info::TypeDef::Variant(variant) = &funcs.type_def { - variant.variants.iter().map(|v| v.fields[0].ty.id) - } else { - unreachable!() - } -} - -fn flat_meta( - service_meta: &AnyServiceMeta, - meta: fn(&AnyServiceMeta) -> &MetaType, -) -> Vec<&MetaType> { - let mut metas = vec![meta(service_meta)]; - for base_service_meta in service_meta.base_services() { - metas.extend(flat_meta(base_service_meta, meta)); - } - metas +fn any_funcs_ids( + registry: &PortableRegistry, + func_type_id: u32, +) -> Result> { + let any_funcs = any_funcs(registry, func_type_id)?; + Ok(any_funcs.into_iter().map(|v| v.fields[0].ty.id)) } pub struct ServiceBuilder { @@ -127,9 +128,9 @@ pub struct ServiceBuilder { impl ServiceBuilder { pub fn new(name: &'static str, meta: AnyServiceMeta) -> Self { let mut registry = Registry::new(); - let commands_type_id = registry.register_type(&meta.commands()).id; - let queries_type_id = registry.register_type(&meta.queries()).id; - let events_type_id = registry.register_type(&meta.events()).id; + let commands_type_id = registry.register_type(meta.commands()).id; + let queries_type_id = registry.register_type(meta.queries()).id; + let events_type_id = registry.register_type(meta.events()).id; let registry = PortableRegistry::from(registry); Self { name, @@ -140,22 +141,17 @@ impl ServiceBuilder { } } - pub fn build(self) -> ServiceUnit { - let mut exclude = HashSet::new(); - exclude.insert(self.commands_type_id); - exclude.extend(any_funcs_ids(&self.registry, self.commands_type_id)); - exclude.insert(self.queries_type_id); - exclude.extend(any_funcs_ids(&self.registry, self.queries_type_id)); - exclude.insert(self.events_type_id); + pub fn build(self) -> Result> { + let exclude = HashSet::from_iter(self.exclude_type_ids()?); let resolver = TypeResolver::from(&self.registry, exclude); - let commands = self.commands(&resolver).unwrap(); - let queries = self.queries(&resolver).unwrap(); - let events = self.events(&resolver).unwrap(); + let commands = self.commands(&resolver)?; + let queries = self.queries(&resolver)?; + let events = self.events(&resolver)?; // let extends = self.extends(); // let services = self.services_expo().clone(); let types = resolver.into_types(); - ServiceUnit { + Ok(vec![ServiceUnit { name: self.name.to_string(), extends: vec![], funcs: [commands, queries].concat(), @@ -163,40 +159,60 @@ impl ServiceBuilder { types, docs: vec![], annotations: vec![], - } + }]) + } + + fn exclude_type_ids(&self) -> Result> { + Ok([ + self.commands_type_id, + self.queries_type_id, + self.events_type_id, + ] + .into_iter() + .chain(any_funcs_ids(&self.registry, self.commands_type_id)?) + .chain(any_funcs_ids(&self.registry, self.queries_type_id)?)) } fn commands(&self, resolver: &TypeResolver) -> Result> { any_funcs(&self.registry, self.commands_type_id)? .map(|c| { - assert_eq!(2, c.fields.len()); - let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); - let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); - let mut throws = None; - // TODO: unwrap result param - if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { - output = ok; - throws = Some(err); - }; - if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { - Ok(ServiceFunc { - name: c.name.to_string(), - params: params_type - .fields - .iter() - .map(|f| FuncParam { - name: f.name.unwrap().to_string(), - type_decl: resolver.get(f.ty.id).unwrap().clone(), - }) - .collect(), - output, - throws, - kind: FunctionKind::Command, - docs: c.docs.iter().map(|s| s.to_string()).collect(), - annotations: vec![], - }) + if c.fields.len() != 2 { + Err(Error::FuncMetaIsInvalid(format!( + "command `{}` has invalid number of fields", + c.name + ))) } else { - unreachable!() + let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); + let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); + let mut throws = None; + // TODO: unwrap result param + if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { + output = ok; + throws = Some(err); + }; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + Ok(ServiceFunc { + name: c.name.to_string(), + params: params_type + .fields + .iter() + .map(|f| FuncParam { + name: f.name.as_ref().unwrap().to_string(), + type_decl: resolver.get(f.ty.id).unwrap().clone(), + }) + .collect(), + output, + throws, + kind: FunctionKind::Command, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![], + }) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "command `{}` params type is not a composite", + c.name + ))) + } } }) .collect() @@ -205,35 +221,44 @@ impl ServiceBuilder { fn queries(&self, resolver: &TypeResolver) -> Result> { any_funcs(&self.registry, self.queries_type_id)? .map(|c| { - assert_eq!(2, c.fields.len()); - let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); - let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); - let mut throws = None; - // TODO: unwrap result param - if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { - output = ok; - throws = Some(err); - }; - if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { - Ok(ServiceFunc { - name: c.name.to_string(), - params: params_type - .fields - .iter() - .map(|f| FuncParam { - name: f.name.unwrap().to_string(), - type_decl: resolver.get(f.ty.id).unwrap().clone(), - }) - .collect(), - output, - // TODO: Throws type - throws, - kind: FunctionKind::Query, - docs: c.docs.iter().map(|s| s.to_string()).collect(), - annotations: vec![("query".to_string(), None)], - }) + if c.fields.len() != 2 { + Err(Error::FuncMetaIsInvalid(format!( + "query `{}` has invalid number of fields", + c.name + ))) } else { - unreachable!() + let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); + let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); + let mut throws = None; + // TODO: unwrap result param + if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { + output = ok; + throws = Some(err); + }; + if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + Ok(ServiceFunc { + name: c.name.to_string(), + params: params_type + .fields + .iter() + .map(|f| FuncParam { + name: f.name.as_ref().unwrap().to_string(), + type_decl: resolver.get(f.ty.id).unwrap().clone(), + }) + .collect(), + output, + // TODO: Throws type + throws, + kind: FunctionKind::Query, + docs: c.docs.iter().map(|s| s.to_string()).collect(), + annotations: vec![("query".to_string(), None)], + }) + } else { + Err(Error::FuncMetaIsInvalid(format!( + "query `{}` params type is not a composite", + c.name + ))) + } } }) .collect() @@ -248,7 +273,7 @@ impl ServiceBuilder { .map(|field| { let type_decl = resolver.get(field.ty.id).unwrap().clone(); StructField { - name: field.name.map(|s| s.to_string()), + name: field.name.as_ref().map(|s| s.to_string()), type_decl, docs: field.docs.iter().map(|d| d.to_string()).collect(), annotations: vec![], diff --git a/rs/idl-gen/src/errors.rs b/rs/idl-gen/src/errors.rs index 97988d871..9923c17b2 100644 --- a/rs/idl-gen/src/errors.rs +++ b/rs/idl-gen/src/errors.rs @@ -12,10 +12,10 @@ pub enum Error { TypeIdIsUnknown(u32), #[error("type `{0}` is not supported")] TypeIsUnsupported(String), + // #[error(transparent)] + // TemplateIsBroken(#[from] Box), + // #[error(transparent)] + // RenderingFailed(#[from] Box), #[error(transparent)] - TemplateIsBroken(#[from] Box), - #[error(transparent)] - RenderingFailed(#[from] Box), - #[error(transparent)] - IoFailed(#[from] std::io::Error), + Io(#[from] std::io::Error), } diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index 8a25a4609..0148a0ca1 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -23,7 +23,7 @@ pub(crate) fn resolve_generic_type_decl( candidates .into_iter() .find(|td| td.to_string() == match_name) - .unwrap_or_else(|| panic!("Not Resolved {}", type_name)) + .unwrap_or_else(|| panic!("Not Resolved {type_name}")) } struct GenericCandidates<'a> { @@ -300,7 +300,7 @@ mod tests { let type_decl = resolver.get(id).unwrap(); let candidates = build_generic_candidates(type_decl, &type_params); - println!("{:?}", candidates); + println!("{candidates:?}"); assert_eq!(2, candidates.len()); assert!(candidates.contains(&Named { diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index 0ab60343d..1bcc1af00 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -59,13 +59,16 @@ pub mod service { } } -fn build_program_ast(name: Option) -> Result { - // let - let service_builders: Vec<_> = P::services() - .map(|(name, meta)| builder::ServiceBuilder::new(name, meta)) - .collect(); - let services: Vec<_> = service_builders.into_iter().map(|b| b.build()).collect(); - let program = name.map(|name| builder::ProgramBuilder::new::

().build(name)); +fn build_program_ast(program_name: Option) -> Result { + let mut services = Vec::new(); + for (name, meta) in P::services() { + services.extend(builder::ServiceBuilder::new(name, meta).build()?); + } + let program = if let Some(name) = program_name { + Some(builder::ProgramBuilder::new::

().build(name)?) + } else { + None + }; let doc = IdlDoc { globals: vec![ ("sails".to_string(), Some(SAILS_VERSION.to_string())), @@ -79,7 +82,7 @@ fn build_program_ast(name: Option) -> Result { } fn build_service_ast(name: &'static str, meta: AnyServiceMeta) -> Result { - let services: Vec<_> = vec![builder::ServiceBuilder::new(name, meta).build()]; + let services = builder::ServiceBuilder::new(name, meta).build()?; let doc = IdlDoc { globals: vec![ ("sails".to_string(), Some(SAILS_VERSION.to_string())), diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index 47495e17e..e65933c19 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -9,7 +9,6 @@ use std::collections::{BTreeMap, HashMap, HashSet}; #[derive(Debug, Clone)] pub struct TypeResolver<'a> { registry: &'a PortableRegistry, - exclude: HashSet, map: HashMap, user_defined: HashMap, } @@ -27,25 +26,18 @@ impl UserDefinedEntry { } impl<'a> TypeResolver<'a> { + #[cfg(test)] pub fn from_registry(registry: &'a PortableRegistry) -> Self { - let mut resolver = Self { - registry, - exclude: HashSet::new(), - map: HashMap::new(), - user_defined: HashMap::new(), - }; - resolver.build_type_decl_map(); - resolver + TypeResolver::from(registry, HashSet::new()) } pub fn from(registry: &'a PortableRegistry, exclude: HashSet) -> Self { let mut resolver = Self { registry, - exclude, map: HashMap::new(), user_defined: HashMap::new(), }; - resolver.build_type_decl_map(); + resolver.build_type_decl_map(exclude); resolver } @@ -63,12 +55,12 @@ impl<'a> TypeResolver<'a> { self.map.get(&key) } - fn build_type_decl_map(&mut self) { + fn build_type_decl_map(&mut self, exclude: HashSet) { let filtered: Vec<_> = self .registry .types .iter() - .filter(|pt| !self.exclude.contains(&pt.id)) + .filter(|pt| !exclude.contains(&pt.id)) .collect(); for pt in filtered { let type_decl = self.resolve_type_decl(&pt.ty); @@ -121,7 +113,7 @@ impl<'a> TypeResolver<'a> { } } TypeDef::Primitive(type_def_primitive) => { - TypeDecl::Primitive(primitive_map(&type_def_primitive)) + TypeDecl::Primitive(primitive_map(type_def_primitive)) } TypeDef::Compact(_) => unimplemented!("TypeDef::Compact is unimplemented"), TypeDef::BitSequence(_) => { @@ -430,7 +422,7 @@ mod tests { let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{:#?}", resolver); + println!("{resolver:#?}"); let h256_decl = resolver.get(h256_id).unwrap(); assert_eq!(*h256_decl, TypeDecl::Primitive(PrimitiveType::H256)); @@ -457,7 +449,7 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{:#?}", resolver); + println!("{resolver:#?}"); let u32_struct = resolver.get(u32_struct_id).unwrap(); assert_eq!(u32_struct.to_string(), "GenericStruct"); @@ -477,7 +469,7 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{:#?}", resolver); + println!("{resolver:#?}"); let u32_string_enum = resolver.get(u32_string_enum_id).unwrap(); assert_eq!(u32_string_enum.to_string(), "GenericEnum"); @@ -550,7 +542,7 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{:#?}", resolver); + println!("{resolver:#?}"); let u32_option = resolver.get(u32_option_id).unwrap(); assert_eq!(u32_option.to_string(), "Option"); @@ -585,7 +577,7 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{:#?}", resolver); + println!("{resolver:#?}"); let btree_map = resolver.get(btree_map_id).unwrap(); assert_eq!(btree_map.to_string(), "[(u32, String)]"); diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index 1b0306533..e7c54a5b6 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -226,7 +226,7 @@ impl ProgramMeta for TestProgramWithEmptyCtorsMeta { type ConstructorsMeta = EmptyCtorsMeta; const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("", AnyServiceMeta::new::)]; + &[("Service", AnyServiceMeta::new::)]; const ASYNC: bool = false; } @@ -258,7 +258,7 @@ impl ProgramMeta for TestProgramWithMultipleServicesMeta { type ConstructorsMeta = EmptyCtorsMeta; const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("", AnyServiceMeta::new::), + ("Service", AnyServiceMeta::new::), ("SomeService", AnyServiceMeta::new::), ]; @@ -270,14 +270,14 @@ fn program_idl_works_with_empty_ctors() { let mut idl = Vec::new(); program::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); + // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + // let generated_idl_program = generated_idl_program.unwrap(); + // assert!(generated_idl_program.ctor().is_none()); + // assert_eq!(generated_idl_program.services().len(), 1); + // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); + // assert_eq!(generated_idl_program.types().len(), 10); } #[test] @@ -285,14 +285,14 @@ fn program_idl_works_with_non_empty_ctors() { let mut idl = Vec::new(); program::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); + // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert_eq!(generated_idl_program.ctor().unwrap().funcs().len(), 2); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + // let generated_idl_program = generated_idl_program.unwrap(); + // assert_eq!(generated_idl_program.ctor().unwrap().funcs().len(), 2); + // assert_eq!(generated_idl_program.services().len(), 1); + // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); + // assert_eq!(generated_idl_program.types().len(), 10); } #[test] @@ -300,17 +300,17 @@ fn program_idl_works_with_multiple_services() { let mut idl = Vec::new(); program::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 2); - assert_eq!(generated_idl_program.services()[0].name(), ""); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.services()[1].name(), "SomeService"); - assert_eq!(generated_idl_program.services()[1].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + + // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); + // let generated_idl_program = generated_idl_program.unwrap(); + // assert!(generated_idl_program.ctor().is_none()); + // assert_eq!(generated_idl_program.services().len(), 2); + // assert_eq!(generated_idl_program.services()[0].name(), ""); + // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); + // assert_eq!(generated_idl_program.services()[1].name(), "SomeService"); + // assert_eq!(generated_idl_program.services()[1].funcs().len(), 4); + // assert_eq!(generated_idl_program.types().len(), 10); } #[test] @@ -318,14 +318,14 @@ fn service_idl_works_with_basics() { let mut idl = Vec::new(); service::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - assert_eq!(generated_idl_program.types().len(), 10); + + // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); + // let generated_idl_program = generated_idl_program.unwrap(); + // assert!(generated_idl_program.ctor().is_none()); + // assert_eq!(generated_idl_program.services().len(), 1); + // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); + // assert_eq!(generated_idl_program.types().len(), 10); } #[test] @@ -341,30 +341,12 @@ fn service_idl_works_with_base_services() { >(&mut idl) .unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - insta::assert_snapshot!(generated_idl); - let generated_idl_program = generated_idl_program.unwrap(); - assert!(generated_idl_program.ctor().is_none()); - assert_eq!(generated_idl_program.services().len(), 1); - assert_eq!(generated_idl_program.services()[0].funcs().len(), 6); - assert_eq!(generated_idl_program.types().len(), 10); -} - -#[test] -fn service_idl_fails_with_base_services_and_ambiguous_events() { - let mut idl = Vec::new(); - let result = service::generate_idl::< - ServiceMetaWithBase< - CommandsMeta, - QueriesMeta, - EventsMeta, - ServiceMeta, - >, - >(&mut idl); - assert!(matches!( - result, - Err(sails_idl_gen::Error::EventMetaIsAmbiguous(_)) - )); + // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); + // let generated_idl_program = generated_idl_program.unwrap(); + // assert!(generated_idl_program.ctor().is_none()); + // assert_eq!(generated_idl_program.services().len(), 1); + // assert_eq!(generated_idl_program.services()[0].funcs().len(), 6); + // assert_eq!(generated_idl_program.types().len(), 10); } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap index c1b0cd014..8db4467cd 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap @@ -4,7 +4,7 @@ expression: generated_idl --- !@sails: 0.9.2 -service { +service Service { events { /// `This` Done ThisDone ( @@ -85,6 +85,6 @@ service { program ProgramToDo { services { - , + Service, } } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap index b9048f1ec..ce2c266d2 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap @@ -4,7 +4,7 @@ expression: generated_idl --- !@sails: 0.9.2 -service { +service Service { events { /// `This` Done ThisDone ( @@ -164,7 +164,7 @@ service SomeService { program ProgramToDo { services { - , + Service, SomeService, } } From 3889e8ce2a4165b65c37edac5fb0027c3e508939 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 2 Dec 2025 17:13:31 +0100 Subject: [PATCH 03/16] wip: builder --- examples/demo/client/demo_client.idl | 26 +++ rs/idl-gen/src/builder.rs | 186 ++++++++++++------ rs/idl-gen/src/lib.rs | 4 +- ..._service_idl_works_with_base_services.snap | 20 ++ 4 files changed, 177 insertions(+), 59 deletions(-) diff --git a/examples/demo/client/demo_client.idl b/examples/demo/client/demo_client.idl index 2cbb64d22..40040f3e5 100644 --- a/examples/demo/client/demo_client.idl +++ b/examples/demo/client/demo_client.idl @@ -25,7 +25,33 @@ service Counter { } } +service TodoBaseName { + functions { + MakeSound() -> String; + @query + AvgWeight() -> u32; + } +} + +service TodoBaseName { + events { + Walked { + from: (i32, i32), + to: (i32, i32), + }, + } + functions { + Walk(dx: i32, dy: i32); + @query + Position() -> (i32, i32); + } +} + service Dog { + extends { + TodoBaseName, + TodoBaseName, + } events { Barked, } diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index f4d90de9d..457177357 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -45,16 +45,29 @@ impl ProgramBuilder { .resolve(params_type_id) .ok_or(Error::TypeIdIsUnknown(params_type_id))?; if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + let params = params_type + .fields + .iter() + .map(|f| -> Result<_> { + let name = f.name.as_ref().ok_or_else(|| { + Error::FuncMetaIsInvalid(format!( + "ctor `{}` param is missing a name", + c.name + )) + })?; + let type_decl = resolver + .get(f.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(f.ty.id))?; + Ok(FuncParam { + name: name.to_string(), + type_decl, + }) + }) + .collect::>>()?; Ok(CtorFunc { name: c.name.to_string(), - params: params_type - .fields - .iter() - .map(|f| FuncParam { - name: f.name.as_ref().unwrap().to_string(), - type_decl: resolver.get(f.ty.id).unwrap().clone(), - }) - .collect(), + params, docs: c.docs.iter().map(|s| s.to_string()).collect(), annotations: vec![], }) @@ -69,17 +82,13 @@ impl ProgramBuilder { .collect() } - fn services_expo(&self) -> &Vec { - &self.services_expo - } - pub fn build(self, name: String) -> Result { let mut exclude = HashSet::new(); exclude.insert(self.ctors_type_id); exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)?); let resolver = TypeResolver::from(&self.registry, exclude); let ctors = self.ctor_funcs(&resolver)?; - let services = self.services_expo().clone(); + let services = self.services_expo; let types = resolver.into_types(); Ok(ProgramUnit { @@ -109,24 +118,31 @@ fn any_funcs( } } -fn any_funcs_ids( - registry: &PortableRegistry, - func_type_id: u32, -) -> Result> { - let any_funcs = any_funcs(registry, func_type_id)?; - Ok(any_funcs.into_iter().map(|v| v.fields[0].ty.id)) +fn any_funcs_ids(registry: &PortableRegistry, func_type_id: u32) -> Result> { + any_funcs(registry, func_type_id)? + .map(|variant| { + variant + .fields + .first() + .map(|field| field.ty.id) + .ok_or_else(|| { + Error::FuncMetaIsInvalid(format!("func `{}` has no fields", variant.name)) + }) + }) + .collect::>>() } -pub struct ServiceBuilder { - name: &'static str, +pub struct ServiceBuilder<'a> { + name: &'a str, + meta: &'a AnyServiceMeta, registry: PortableRegistry, commands_type_id: u32, queries_type_id: u32, events_type_id: u32, } -impl ServiceBuilder { - pub fn new(name: &'static str, meta: AnyServiceMeta) -> Self { +impl<'a> ServiceBuilder<'a> { + pub fn new(name: &'a str, meta: &'a AnyServiceMeta) -> Self { let mut registry = Registry::new(); let commands_type_id = registry.register_type(meta.commands()).id; let queries_type_id = registry.register_type(meta.queries()).id; @@ -134,6 +150,7 @@ impl ServiceBuilder { let registry = PortableRegistry::from(registry); Self { name, + meta, registry, commands_type_id, queries_type_id, @@ -142,35 +159,45 @@ impl ServiceBuilder { } pub fn build(self) -> Result> { + let mut services = Vec::new(); + let mut extends = Vec::new(); + for meta in self.meta.base_services() { + // TODO: add base service names to Meta trait + let name = "TodoBaseName"; + extends.push(name.to_string()); + // TODO: dedup base services based on `interface_id` + services.extend(ServiceBuilder::new(name, meta).build()?); + } + let exclude = HashSet::from_iter(self.exclude_type_ids()?); let resolver = TypeResolver::from(&self.registry, exclude); let commands = self.commands(&resolver)?; let queries = self.queries(&resolver)?; let events = self.events(&resolver)?; - // let extends = self.extends(); - // let services = self.services_expo().clone(); let types = resolver.into_types(); - Ok(vec![ServiceUnit { + services.push(ServiceUnit { name: self.name.to_string(), - extends: vec![], + extends, funcs: [commands, queries].concat(), events, types, docs: vec![], annotations: vec![], - }]) + }); + Ok(services) } fn exclude_type_ids(&self) -> Result> { - Ok([ + let base = vec![ self.commands_type_id, self.queries_type_id, self.events_type_id, ] - .into_iter() - .chain(any_funcs_ids(&self.registry, self.commands_type_id)?) - .chain(any_funcs_ids(&self.registry, self.queries_type_id)?)) + .into_iter(); + let command_ids = any_funcs_ids(&self.registry, self.commands_type_id)?; + let query_ids = any_funcs_ids(&self.registry, self.queries_type_id)?; + Ok(base.chain(command_ids).chain(query_ids)) } fn commands(&self, resolver: &TypeResolver) -> Result> { @@ -182,8 +209,16 @@ impl ServiceBuilder { c.name ))) } else { - let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); - let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); + let params_type_id = c.fields[0].ty.id; + let params_type = self + .registry + .resolve(params_type_id) + .ok_or(Error::TypeIdIsUnknown(params_type_id))?; + let output_type_id = c.fields[1].ty.id; + let mut output = resolver + .get(output_type_id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(output_type_id))?; let mut throws = None; // TODO: unwrap result param if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { @@ -191,16 +226,29 @@ impl ServiceBuilder { throws = Some(err); }; if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + let params = params_type + .fields + .iter() + .map(|f| -> Result<_> { + let name = f.name.as_ref().ok_or_else(|| { + Error::FuncMetaIsInvalid(format!( + "command `{}` param is missing a name", + c.name + )) + })?; + let type_decl = resolver + .get(f.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(f.ty.id))?; + Ok(FuncParam { + name: name.to_string(), + type_decl, + }) + }) + .collect::>>()?; Ok(ServiceFunc { name: c.name.to_string(), - params: params_type - .fields - .iter() - .map(|f| FuncParam { - name: f.name.as_ref().unwrap().to_string(), - type_decl: resolver.get(f.ty.id).unwrap().clone(), - }) - .collect(), + params, output, throws, kind: FunctionKind::Command, @@ -227,8 +275,16 @@ impl ServiceBuilder { c.name ))) } else { - let params_type = self.registry.resolve(c.fields[0].ty.id).unwrap(); - let mut output = resolver.get(c.fields[1].ty.id).unwrap().clone(); + let params_type_id = c.fields[0].ty.id; + let params_type = self + .registry + .resolve(params_type_id) + .ok_or(Error::TypeIdIsUnknown(params_type_id))?; + let output_type_id = c.fields[1].ty.id; + let mut output = resolver + .get(output_type_id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(output_type_id))?; let mut throws = None; // TODO: unwrap result param if let Some((ok, err)) = TypeDecl::result_type_decl(&output) { @@ -236,16 +292,29 @@ impl ServiceBuilder { throws = Some(err); }; if let scale_info::TypeDef::Composite(params_type) = ¶ms_type.type_def { + let params = params_type + .fields + .iter() + .map(|f| -> Result<_> { + let name = f.name.as_ref().ok_or_else(|| { + Error::FuncMetaIsInvalid(format!( + "query `{}` param is missing a name", + c.name + )) + })?; + let type_decl = resolver + .get(f.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(f.ty.id))?; + Ok(FuncParam { + name: name.to_string(), + type_decl, + }) + }) + .collect::>>()?; Ok(ServiceFunc { name: c.name.to_string(), - params: params_type - .fields - .iter() - .map(|f| FuncParam { - name: f.name.as_ref().unwrap().to_string(), - type_decl: resolver.get(f.ty.id).unwrap().clone(), - }) - .collect(), + params, output, // TODO: Throws type throws, @@ -270,16 +339,19 @@ impl ServiceBuilder { let fields = v .fields .iter() - .map(|field| { - let type_decl = resolver.get(field.ty.id).unwrap().clone(); - StructField { + .map(|field| -> Result<_> { + let type_decl = resolver + .get(field.ty.id) + .cloned() + .ok_or(Error::TypeIdIsUnknown(field.ty.id))?; + Ok(StructField { name: field.name.as_ref().map(|s| s.to_string()), type_decl, docs: field.docs.iter().map(|d| d.to_string()).collect(), annotations: vec![], - } + }) }) - .collect(); + .collect::>>()?; Ok(ServiceEvent { name: v.name.to_string(), diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index 1bcc1af00..f419fdfc7 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -62,7 +62,7 @@ pub mod service { fn build_program_ast(program_name: Option) -> Result { let mut services = Vec::new(); for (name, meta) in P::services() { - services.extend(builder::ServiceBuilder::new(name, meta).build()?); + services.extend(builder::ServiceBuilder::new(name, &meta).build()?); } let program = if let Some(name) = program_name { Some(builder::ProgramBuilder::new::

().build(name)?) @@ -82,7 +82,7 @@ fn build_program_ast(program_name: Option) -> Result Result { - let services = builder::ServiceBuilder::new(name, meta).build()?; + let services = builder::ServiceBuilder::new(name, &meta).build()?; let doc = IdlDoc { globals: vec![ ("sails".to_string(), Some(SAILS_VERSION.to_string())), diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap index 223cb0972..823ba16e6 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -4,7 +4,27 @@ expression: generated_idl --- !@sails: 0.9.2 +service TodoBaseName { + events { + ThisDoneBase(u32), + ThatDoneBase { + p1: u16, + }, + } + functions { + DoThis(p1: u32) -> u32; + DoThatBase(p1: String) -> String; + @query + ThisBase(p1: u16) -> u16; + @query + That(p1: String) -> String; + } +} + service ServiceToDo { + extends { + TodoBaseName, + } events { /// `This` Done ThisDone ( From 24d0603508fbc7a960aa88cbcc493db89e972d29 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Fri, 5 Dec 2025 17:56:17 +0100 Subject: [PATCH 04/16] wip: idl-gen, examples work --- Cargo.lock | 2 +- .../ping-pong/app/src/ping_pong_client.rs | 20 +- benchmarks/src/alloc_stress_program.rs | 14 +- examples/demo/app/tests/gtest.rs | 120 +++---- examples/demo/client/build.rs | 15 +- examples/demo/client/demo_client.idl | 22 +- examples/demo/client/src/demo_client.rs | 293 +++++++++++++----- examples/demo/client/src/lib.rs | 8 +- examples/no-svcs-prog/wasm/build.rs | 13 +- examples/proxy/src/this_that/mod.rs | 5 +- examples/redirect/client/build.rs | 13 +- examples/redirect/proxy-client/build.rs | 11 +- .../proxy-client/redirect_proxy_client.idl | 21 +- .../rmrk/resource/app/src/rmrk_catalog.rs | 130 ++++---- rs/client-gen/src/root_generator.rs | 32 +- rs/client-gen/src/service_generators.rs | 43 ++- .../snapshots/generator__basic_works.snap | 22 +- ...erator__complex_type_generation_works.snap | 31 +- .../snapshots/generator__events_works.snap | 22 +- .../snapshots/generator__external_types.snap | 9 - .../generator__full_with_sails_path.snap | 6 +- .../generator__multiple_services.snap | 31 +- .../snapshots/generator__rmrk_works.snap | 22 +- .../generator__scope_resolution.snap | 22 +- rs/idl-gen/Cargo.toml | 2 +- rs/idl-gen/src/builder.rs | 4 +- rs/idl-gen/tests/generator.rs | 78 +++-- ...rogram_idl_works_with_non_empty_ctors.snap | 4 +- ..._service_idl_works_with_base_services.snap | 4 +- rs/src/builder.rs | 2 +- 30 files changed, 581 insertions(+), 440 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57f87c9de..f17b40f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7234,7 +7234,7 @@ dependencies = [ "insta", "quote", "sails-idl-meta", - "sails-idl-parser", + "sails-idl-parser-v2", "scale-info", "serde", "serde_json", diff --git a/benchmarks/ping-pong/app/src/ping_pong_client.rs b/benchmarks/ping-pong/app/src/ping_pong_client.rs index 4549bd880..523c84aea 100644 --- a/benchmarks/ping-pong/app/src/ping_pong_client.rs +++ b/benchmarks/ping-pong/app/src/ping_pong_client.rs @@ -38,19 +38,19 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(NewForBench () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub enum PingPongPayload { - Start(ActorId), - Ping, - Pong, - Finished, -} pub mod ping_pong_service { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub enum PingPongPayload { + Start(ActorId), + Ping, + Pong, + Finished, + } pub trait PingPongService { type Env: sails_rs::client::GearEnv; fn ping( diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index a94895f21..b239a4f31 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -36,16 +36,16 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(NewForBench () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct AllocStressResult { - pub inner: Vec, -} pub mod alloc_stress { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct AllocStressResult { + pub inner: Vec, + } pub trait AllocStress { type Env: sails_rs::client::GearEnv; fn alloc_stress( diff --git a/examples/demo/app/tests/gtest.rs b/examples/demo/app/tests/gtest.rs index e4213e1e3..b43e2494d 100644 --- a/examples/demo/app/tests/gtest.rs +++ b/examples/demo/app/tests/gtest.rs @@ -230,66 +230,66 @@ async fn dog_barks() { assert_eq!((demo_program.id(), DogEvents::Barked), event); } -#[tokio::test] -async fn dog_walks() { - use demo_client::dog::{Dog as _, events::DogEvents}; - // Arrange - let (env, code_id, _gas_limit) = create_env(); - - // Use generated client code for activating Demo program - // using the `new` constructor and the `send`/`recv` pair - // of methods - let demo_program = env - .deploy(code_id, vec![]) - .new(None, Some((1, -1))) - .await - .unwrap(); - - let mut dog_client = demo_program.dog(); - let dog_listener = dog_client.listener(); - let mut dog_events = dog_listener.listen().await.unwrap(); - - // Act - dog_client.walk(10, 20).await.unwrap(); - - // Assert - let position = dog_client.position().await.unwrap(); - let event = dog_events.next().await.unwrap(); - - assert_eq!(position, (11, 19)); - assert_eq!( - ( - demo_program.id(), - DogEvents::Walked { - from: (1, -1), - to: (11, 19) - } - ), - event - ); -} - -#[tokio::test] -async fn dog_weights() { - use demo_client::dog::Dog as _; - // Arrange - let (env, code_id, _gas_limit) = create_env(); - - // Use generated client code for activating Demo program - // using the `new` constructor and the `send`/`recv` pair - // of methods - let demo_program = env - .deploy(code_id, vec![]) - .new(None, Some((1, -1))) - .await - .unwrap(); - - let dog_client = demo_program.dog(); - - let avg_weight = dog_client.avg_weight().await.unwrap(); - - assert_eq!(avg_weight, 42); -} +// #[tokio::test] +// async fn dog_walks() { +// use demo_client::walker_service::{WalkerService as _, events::WalkerServiceEvents}; +// // Arrange +// let (env, code_id, _gas_limit) = create_env(); + +// // Use generated client code for activating Demo program +// // using the `new` constructor and the `send`/`recv` pair +// // of methods +// let demo_program = env +// .deploy(code_id, vec![]) +// .new(None, Some((1, -1))) +// .await +// .unwrap(); + +// let mut walker_client = demo_program.walker_service(); +// let listener = walker_client.listener(); +// let mut events = dog_listener.listen().await.unwrap(); + +// // Act +// walker_client.walk(10, 20).await.unwrap(); + +// // Assert +// let position = walker_client.position().await.unwrap(); +// let event = events.next().await.unwrap(); + +// assert_eq!(position, (11, 19)); +// assert_eq!( +// ( +// demo_program.id(), +// WalkerServiceEvents::Walked { +// from: (1, -1), +// to: (11, 19) +// } +// ), +// event +// ); +// } + +// #[tokio::test] +// async fn dog_weights() { +// use demo_client::dog::Dog as _; +// // Arrange +// let (env, code_id, _gas_limit) = create_env(); + +// // Use generated client code for activating Demo program +// // using the `new` constructor and the `send`/`recv` pair +// // of methods +// let demo_program = env +// .deploy(code_id, vec![]) +// .new(None, Some((1, -1))) +// .await +// .unwrap(); + +// let dog_client = demo_program.dog(); + +// let avg_weight = dog_client.avg_weight().await.unwrap(); + +// assert_eq!(avg_weight, 42); +// } #[tokio::test] async fn references_add() { diff --git a/examples/demo/client/build.rs b/examples/demo/client/build.rs index 9d42f3848..d3ab1bcb6 100644 --- a/examples/demo/client/build.rs +++ b/examples/demo/client/build.rs @@ -1,15 +1,10 @@ -use sails_rs::ClientGenerator; -use std::{env, path::PathBuf}; - fn main() { // Generate IDL file for the `Demo` app and client code from IDL file - // sails_rs::ClientBuilder::::from_env().build_idl(); - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("demo_client.idl"); - let client_rs_file_path = manifest_dir.join("src/demo_client.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) + sails_rs::ClientBuilder::::from_env() + .build_idl() .with_mocks("with_mocks") - .generate_to(client_rs_file_path) + .with_external_type("NonZeroU8", "core::num::NonZeroU8") + .with_external_type("NonZeroU32", "core::num::NonZeroU32") + .generate() .unwrap(); } diff --git a/examples/demo/client/demo_client.idl b/examples/demo/client/demo_client.idl index 40040f3e5..f3a6baf60 100644 --- a/examples/demo/client/demo_client.idl +++ b/examples/demo/client/demo_client.idl @@ -25,15 +25,7 @@ service Counter { } } -service TodoBaseName { - functions { - MakeSound() -> String; - @query - AvgWeight() -> u32; - } -} - -service TodoBaseName { +service WalkerService { events { Walked { from: (i32, i32), @@ -47,10 +39,18 @@ service TodoBaseName { } } +service MammalService { + functions { + MakeSound() -> String; + @query + AvgWeight() -> u32; + } +} + service Dog { extends { - TodoBaseName, - TodoBaseName, + WalkerService, + MammalService, } events { Barked, diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index 32442bfd1..dd9eab166 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -1,6 +1,14 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] +use core::num::NonZeroU8; +#[allow(unused_imports)] +use core::num::NonZeroU32; +#[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct DemoClientProgram; impl sails_rs::client::Program for DemoClientProgram {} pub trait DemoClient { @@ -12,6 +20,12 @@ pub trait DemoClient { fn this_that(&self) -> sails_rs::client::Service; fn value_fee(&self) -> sails_rs::client::Service; fn chaos(&self) -> sails_rs::client::Service; + fn walker_service( + &self, + ) -> sails_rs::client::Service; + fn mammal_service( + &self, + ) -> sails_rs::client::Service; } impl DemoClient for sails_rs::client::Actor { type Env = E; @@ -36,6 +50,16 @@ impl DemoClient for sails_rs::client::Actor sails_rs::client::Service { self.service(stringify!(Chaos)) } + fn walker_service( + &self, + ) -> sails_rs::client::Service { + self.service(stringify!(WalkerService)) + } + fn mammal_service( + &self, + ) -> sails_rs::client::Service { + self.service(stringify!(MammalService)) + } } pub trait DemoClientCtors { type Env: sails_rs::client::GearEnv; @@ -71,49 +95,6 @@ pub mod io { sails_rs::io_struct_impl!(Default () -> ()); sails_rs::io_struct_impl!(New (counter: super::Option, dog_position: super::Option<(i32, i32, ), >) -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct ReferenceCount(pub u32); -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct DoThatParam { - pub p1: NonZeroU32, - pub p2: ActorId, - pub p3: ManyVariants, -} -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub enum ManyVariants { - One, - Two(u32), - Three(Option), - Four { a: u32, b: Option }, - Five(String, H256), - Six(u32), -} -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub enum ManyVariantsReply { - One, - Two, - Three, - Four, - Five, - Six, -} -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct TupleStruct(pub bool); pub mod ping_pong { use super::*; @@ -131,7 +112,15 @@ pub mod ping_pong { pub mod io { use super::*; - sails_rs::io_struct_impl!(Ping (input: String) -> super::Result); + sails_rs::io_struct_impl!(Ping (input: String) -> Result); + } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = sails_rs::client::GstdEnv; fn ping (&mut self, input: String) -> sails_rs::client::PendingCall; } } } } @@ -186,30 +175,84 @@ pub mod counter { type Event = CounterEvents; } } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = sails_rs::client::GstdEnv; fn add (&mut self, value: u32) -> sails_rs::client::PendingCall;fn sub (&mut self, value: u32) -> sails_rs::client::PendingCall;fn value (&self, ) -> sails_rs::client::PendingCall; } } + } } -pub mod dog { +pub mod walker_service { use super::*; - pub trait Dog { + pub trait WalkerService { type Env: sails_rs::client::GearEnv; - fn make_sound(&mut self) -> sails_rs::client::PendingCall; fn walk(&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall; - fn avg_weight(&self) -> sails_rs::client::PendingCall; fn position(&self) -> sails_rs::client::PendingCall; } - pub struct DogImpl; - impl Dog for sails_rs::client::Service { + pub struct WalkerServiceImpl; + impl WalkerService + for sails_rs::client::Service + { type Env = E; - fn make_sound(&mut self) -> sails_rs::client::PendingCall { - self.pending_call(()) - } fn walk(&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall { self.pending_call((dx, dy)) } - fn avg_weight(&self) -> sails_rs::client::PendingCall { + fn position(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } - fn position(&self) -> sails_rs::client::PendingCall { + } + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(Walk (dx: i32, dy: i32) -> ()); + sails_rs::io_struct_impl!(Position () -> (i32, i32, )); + } + + #[cfg(not(target_arch = "wasm32"))] + pub mod events { + use super::*; + #[derive(PartialEq, Debug, Encode, Decode, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[reflect_hash(crate = sails_rs)] + pub enum WalkerServiceEvents { + Walked { from: (i32, i32), to: (i32, i32) }, + } + impl sails_rs::client::Event for WalkerServiceEvents { + const EVENT_NAMES: &'static [Route] = &["Walked"]; + } + impl sails_rs::client::ServiceWithEvents for WalkerServiceImpl { + type Event = WalkerServiceEvents; + } + } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub WalkerService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl walker_service::WalkerService for WalkerService { type Env = sails_rs::client::GstdEnv; fn walk (&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall;fn position (&self, ) -> sails_rs::client::PendingCall; } } + } +} + +pub mod mammal_service { + use super::*; + pub trait MammalService { + type Env: sails_rs::client::GearEnv; + fn make_sound(&mut self) -> sails_rs::client::PendingCall; + fn avg_weight(&self) -> sails_rs::client::PendingCall; + } + pub struct MammalServiceImpl; + impl MammalService + for sails_rs::client::Service + { + type Env = E; + fn make_sound(&mut self) -> sails_rs::client::PendingCall { + self.pending_call(()) + } + fn avg_weight(&self) -> sails_rs::client::PendingCall { self.pending_call(()) } } @@ -217,9 +260,35 @@ pub mod dog { pub mod io { use super::*; sails_rs::io_struct_impl!(MakeSound () -> String); - sails_rs::io_struct_impl!(Walk (dx: i32, dy: i32) -> ()); sails_rs::io_struct_impl!(AvgWeight () -> u32); - sails_rs::io_struct_impl!(Position () -> (i32, i32, )); + } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub MammalService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl mammal_service::MammalService for MammalService { type Env = sails_rs::client::GstdEnv; fn make_sound (&mut self, ) -> sails_rs::client::PendingCall;fn avg_weight (&self, ) -> sails_rs::client::PendingCall; } } + } +} + +pub mod dog { + use super::*; + pub trait Dog { + type Env: sails_rs::client::GearEnv; + fn make_sound(&mut self) -> sails_rs::client::PendingCall; + } + pub struct DogImpl; + impl Dog for sails_rs::client::Service { + type Env = E; + fn make_sound(&mut self) -> sails_rs::client::PendingCall { + self.pending_call(()) + } + } + + pub mod io { + use super::*; + sails_rs::io_struct_impl!(MakeSound () -> String); } #[cfg(not(target_arch = "wasm32"))] @@ -230,19 +299,31 @@ pub mod dog { #[reflect_hash(crate = sails_rs)] pub enum DogEvents { Barked, - Walked { from: (i32, i32), to: (i32, i32) }, } impl sails_rs::client::Event for DogEvents { - const EVENT_NAMES: &'static [Route] = &["Barked", "Walked"]; + const EVENT_NAMES: &'static [Route] = &["Barked"]; } impl sails_rs::client::ServiceWithEvents for DogImpl { type Event = DogEvents; } } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = sails_rs::client::GstdEnv; fn make_sound (&mut self, ) -> sails_rs::client::PendingCall; } } + } } pub mod references { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct ReferenceCount(pub u32); pub trait References { type Env: sails_rs::client::GearEnv; fn add(&mut self, v: u32) -> sails_rs::client::PendingCall; @@ -293,17 +374,63 @@ pub mod references { use super::*; sails_rs::io_struct_impl!(Add (v: u32) -> u32); sails_rs::io_struct_impl!(AddByte (byte: u8) -> Vec); - sails_rs::io_struct_impl!(GuessNum (number: u8) -> super::Result); + sails_rs::io_struct_impl!(GuessNum (number: u8) -> Result); sails_rs::io_struct_impl!(Incr () -> super::ReferenceCount); - sails_rs::io_struct_impl!(SetNum (number: u8) -> super::Result<(), String, >); + sails_rs::io_struct_impl!(SetNum (number: u8) -> Result<(), String>); sails_rs::io_struct_impl!(Baked () -> String); sails_rs::io_struct_impl!(LastByte () -> super::Option); sails_rs::io_struct_impl!(Message () -> super::Option); } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = sails_rs::client::GstdEnv; fn add (&mut self, v: u32) -> sails_rs::client::PendingCall;fn add_byte (&mut self, byte: u8) -> sails_rs::client::PendingCall;fn guess_num (&mut self, number: u8) -> sails_rs::client::PendingCall;fn incr (&mut self, ) -> sails_rs::client::PendingCall;fn set_num (&mut self, number: u8) -> sails_rs::client::PendingCall;fn baked (&self, ) -> sails_rs::client::PendingCall;fn last_byte (&self, ) -> sails_rs::client::PendingCall;fn message (&self, ) -> sails_rs::client::PendingCall; } } + } } pub mod this_that { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct DoThatParam { + pub p1: NonZeroU32, + pub p2: ActorId, + pub p3: ManyVariants, + } + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub enum ManyVariants { + One, + Two(u32), + Three(Option), + Four { a: u32, b: Option }, + Five(String, H256), + Six((u32,)), + } + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub enum ManyVariantsReply { + One, + Two, + Three, + Four, + Five, + Six, + } + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct TupleStruct(pub bool); pub trait ThisThat { type Env: sails_rs::client::GearEnv; fn do_that( @@ -352,12 +479,20 @@ pub mod this_that { pub mod io { use super::*; - sails_rs::io_struct_impl!(DoThat (param: super::DoThatParam) -> super::Result<(ActorId, super::NonZeroU32, super::ManyVariantsReply, ), (String, ), >); + sails_rs::io_struct_impl!(DoThat (param: super::DoThatParam) -> Result<(ActorId, super::NonZeroU32, super::ManyVariantsReply, ), (String, )>); sails_rs::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (super::Option, super::NonZeroU8, ), p4: super::TupleStruct) -> (String, u32, )); sails_rs::io_struct_impl!(Noop () -> ()); - sails_rs::io_struct_impl!(That () -> super::Result); + sails_rs::io_struct_impl!(That () -> Result); sails_rs::io_struct_impl!(This () -> u32); } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = sails_rs::client::GstdEnv; fn do_that (&mut self, param: DoThatParam) -> sails_rs::client::PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option, NonZeroU8, ), p4: TupleStruct) -> sails_rs::client::PendingCall;fn noop (&mut self, ) -> sails_rs::client::PendingCall;fn that (&self, ) -> sails_rs::client::PendingCall;fn this (&self, ) -> sails_rs::client::PendingCall; } } + } } pub mod value_fee { @@ -401,6 +536,14 @@ pub mod value_fee { type Event = ValueFeeEvents; } } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = sails_rs::client::GstdEnv; fn do_something_and_take_fee (&mut self, ) -> sails_rs::client::PendingCall; } } + } } pub mod chaos { @@ -435,22 +578,12 @@ pub mod chaos { sails_rs::io_struct_impl!(ReplyHookCounter () -> u32); sails_rs::io_struct_impl!(TimeoutWait () -> ()); } -} -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub PingPong {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl ping_pong::PingPong for PingPong { type Env = sails_rs::client::GstdEnv; fn ping (&mut self, input: String) -> sails_rs::client::PendingCall; } } - mock! { pub Counter {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl counter::Counter for Counter { type Env = sails_rs::client::GstdEnv; fn add (&mut self, value: u32) -> sails_rs::client::PendingCall;fn sub (&mut self, value: u32) -> sails_rs::client::PendingCall;fn value (&self, ) -> sails_rs::client::PendingCall; } } - mock! { pub Dog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl dog::Dog for Dog { type Env = sails_rs::client::GstdEnv; fn make_sound (&mut self, ) -> sails_rs::client::PendingCall;fn walk (&mut self, dx: i32, dy: i32) -> sails_rs::client::PendingCall;fn avg_weight (&self, ) -> sails_rs::client::PendingCall;fn position (&self, ) -> sails_rs::client::PendingCall; } } - mock! { pub References {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl references::References for References { type Env = sails_rs::client::GstdEnv; fn add (&mut self, v: u32) -> sails_rs::client::PendingCall;fn add_byte (&mut self, byte: u8) -> sails_rs::client::PendingCall;fn guess_num (&mut self, number: u8) -> sails_rs::client::PendingCall;fn incr (&mut self, ) -> sails_rs::client::PendingCall;fn set_num (&mut self, number: u8) -> sails_rs::client::PendingCall;fn baked (&self, ) -> sails_rs::client::PendingCall;fn last_byte (&self, ) -> sails_rs::client::PendingCall;fn message (&self, ) -> sails_rs::client::PendingCall; } } - mock! { pub ThisThat {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl this_that::ThisThat for ThisThat { type Env = sails_rs::client::GstdEnv; fn do_that (&mut self, param: DoThatParam) -> sails_rs::client::PendingCall;fn do_this (&mut self, p1: u32, p2: String, p3: (Option, NonZeroU8, ), p4: TupleStruct) -> sails_rs::client::PendingCall;fn noop (&mut self, ) -> sails_rs::client::PendingCall;fn that (&self, ) -> sails_rs::client::PendingCall;fn this (&self, ) -> sails_rs::client::PendingCall; } } - mock! { pub ValueFee {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl value_fee::ValueFee for ValueFee { type Env = sails_rs::client::GstdEnv; fn do_something_and_take_fee (&mut self, ) -> sails_rs::client::PendingCall; } } - mock! { pub Chaos {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl chaos::Chaos for Chaos { type Env = sails_rs::client::GstdEnv; fn panic_after_wait (&self, ) -> sails_rs::client::PendingCall;fn reply_hook_counter (&self, ) -> sails_rs::client::PendingCall;fn timeout_wait (&self, ) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub Chaos {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl chaos::Chaos for Chaos { type Env = sails_rs::client::GstdEnv; fn panic_after_wait (&self, ) -> sails_rs::client::PendingCall;fn reply_hook_counter (&self, ) -> sails_rs::client::PendingCall;fn timeout_wait (&self, ) -> sails_rs::client::PendingCall; } } + } } diff --git a/examples/demo/client/src/lib.rs b/examples/demo/client/src/lib.rs index 4626a0625..bc81cfaf6 100644 --- a/examples/demo/client/src/lib.rs +++ b/examples/demo/client/src/lib.rs @@ -9,7 +9,9 @@ mod tests { #[test] fn test_io_module_encode() { - let bytes = this_that::io::DoThat::encode_params_with_prefix( + use this_that::*; + + let bytes = io::DoThat::encode_params_with_prefix( "ThisThat", DoThatParam { p1: NonZeroU32::MAX, @@ -33,6 +35,8 @@ mod tests { #[test] fn test_io_module_decode_reply() { + use this_that::*; + let bytes = vec![ 32, 84, 104, 105, 115, 84, 104, 97, 116, // ThisThat 24, 68, 111, 84, 104, 97, 116, // DoThat @@ -44,7 +48,7 @@ mod tests { ]; let reply: Result<(ActorId, NonZeroU32, ManyVariantsReply), (String,)> = - this_that::io::DoThat::decode_reply_with_prefix("ThisThat", bytes).unwrap(); + io::DoThat::decode_reply_with_prefix("ThisThat", bytes).unwrap(); assert_eq!( reply, diff --git a/examples/no-svcs-prog/wasm/build.rs b/examples/no-svcs-prog/wasm/build.rs index 5cb10f108..3fb4913e3 100644 --- a/examples/no-svcs-prog/wasm/build.rs +++ b/examples/no-svcs-prog/wasm/build.rs @@ -1,16 +1,5 @@ fn main() { sails_rs::build_wasm(); - // sails_rs::build_client::(); - - use sails_rs::ClientGenerator; - use std::{env, path::PathBuf}; - - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("no_svcs_prog.idl"); - let client_rs_file_path = manifest_dir.join("src/no_svcs_prog.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) - .generate_to(client_rs_file_path) - .unwrap(); + sails_rs::build_client::(); } diff --git a/examples/proxy/src/this_that/mod.rs b/examples/proxy/src/this_that/mod.rs index 423ff4fd0..567d723e3 100644 --- a/examples/proxy/src/this_that/mod.rs +++ b/examples/proxy/src/this_that/mod.rs @@ -1,5 +1,6 @@ -use demo_client::{this_that::ThisThat, *}; +use demo_client::{this_that::ThisThat, this_that::*}; use sails_rename::{client::*, gstd::Syscall, prelude::*}; + #[derive(Clone)] pub struct ThisThatCaller { this_that: ThisThatClient, @@ -47,7 +48,7 @@ where #[cfg(test)] mod tests { use super::*; - use demo_client::mockall::MockThisThat; + use demo_client::this_that::mockall::MockThisThat; use sails_rename::{client::PendingCall, gstd::services::Service}; #[tokio::test] diff --git a/examples/redirect/client/build.rs b/examples/redirect/client/build.rs index 1910aca45..91b9e35ea 100644 --- a/examples/redirect/client/build.rs +++ b/examples/redirect/client/build.rs @@ -1,14 +1,3 @@ -use sails_rs::ClientGenerator; -use std::{env, path::PathBuf}; - fn main() { - // sails_rs::build_client::(); - - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("redirect_client.idl"); - let client_rs_file_path = manifest_dir.join("src/redirect_client.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) - .generate_to(client_rs_file_path) - .unwrap(); + sails_rs::build_client::(); } diff --git a/examples/redirect/proxy-client/build.rs b/examples/redirect/proxy-client/build.rs index 34b82c0f4..c101ea40b 100644 --- a/examples/redirect/proxy-client/build.rs +++ b/examples/redirect/proxy-client/build.rs @@ -1,12 +1,3 @@ -use sails_rs::ClientGenerator; -use std::{env, path::PathBuf}; - fn main() { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let idl_file_path = manifest_dir.join("redirect_proxy_client.idl"); - let client_rs_file_path = manifest_dir.join("src/redirect_proxy_client.rs"); - - ClientGenerator::from_idl_path(&idl_file_path) - .generate_to(client_rs_file_path) - .unwrap(); + sails_rs::build_client::(); } diff --git a/examples/redirect/proxy-client/redirect_proxy_client.idl b/examples/redirect/proxy-client/redirect_proxy_client.idl index ba1b9c995..758e4307e 100644 --- a/examples/redirect/proxy-client/redirect_proxy_client.idl +++ b/examples/redirect/proxy-client/redirect_proxy_client.idl @@ -1,4 +1,15 @@ -program Proxy { + +!@sails: 0.9.2 + +service Proxy { + functions { + /// Get program ID of the target program via client + @query + GetProgramId() -> ActorId; + } +} + +program ProgramToDo { constructors { /// Proxy Program's constructor New(target: ActorId); @@ -7,11 +18,3 @@ program Proxy { Proxy, } } - -service Proxy { - functions { - /// Get program ID of the target program via client - @query - GetProgramId() -> ActorId; - } -} \ No newline at end of file diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs index 993f74219..e984b482d 100644 --- a/examples/rmrk/resource/app/src/rmrk_catalog.rs +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -1,6 +1,10 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "mockall")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct RmrkCatalogProgram; impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { @@ -32,56 +36,56 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(New () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub enum Error { - PartIdCantBeZero, - BadConfig, - PartAlreadyExists, - ZeroLengthPassed, - PartDoesNotExist, - WrongPartFormat, - NotAllowedToCall, -} -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub enum Part { - Fixed(FixedPart), - Slot(SlotPart), -} -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct FixedPart { - /// An optional zIndex of base part layer. - /// specifies the stack order of an element. - /// An element with greater stack order is always in front of an element with a lower stack order. - pub z: Option, - /// The metadata URI of the part. - pub metadata_uri: String, -} -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct SlotPart { - /// Array of whitelisted collections that can be equipped in the given slot. Used with slot parts only. - pub equippable: Vec, - /// An optional zIndex of base part layer. - /// specifies the stack order of an element. - /// An element with greater stack order is always in front of an element with a lower stack order. - pub z: Option, - /// The metadata URI of the part. - pub metadata_uri: String, -} pub mod rmrk_catalog { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub enum Error { + PartIdCantBeZero, + BadConfig, + PartAlreadyExists, + ZeroLengthPassed, + PartDoesNotExist, + WrongPartFormat, + NotAllowedToCall, + } + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct FixedPart { + /// An optional zIndex of base part layer. + /// specifies the stack order of an element. + /// An element with greater stack order is always in front of an element with a lower stack order. + pub z: Option, + /// The metadata URI of the part. + pub metadata_uri: String, + } + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub enum Part { + Fixed(FixedPart), + Slot(SlotPart), + } + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct SlotPart { + /// Array of whitelisted collections that can be equipped in the given slot. Used with slot parts only. + pub equippable: Vec, + /// An optional zIndex of base part layer. + /// specifies the stack order of an element. + /// An element with greater stack order is always in front of an element with a lower stack order. + pub z: Option, + /// The metadata URI of the part. + pub metadata_uri: String, + } pub trait RmrkCatalog { type Env: sails_rs::client::GearEnv; fn add_equippables( @@ -172,25 +176,21 @@ pub mod rmrk_catalog { pub mod io { use super::*; - sails_rs::io_struct_impl!(AddEquippables (part_id: u32, collection_ids: Vec) -> super::Result<(u32, Vec, ), super::Error, >); - sails_rs::io_struct_impl!(AddParts (parts: Vec<(u32, super::Part, )>) -> super::Result, super::Error, >); - sails_rs::io_struct_impl!(RemoveEquippable (part_id: u32, collection_id: ActorId) -> super::Result<(u32, ActorId, ), super::Error, >); - sails_rs::io_struct_impl!(RemoveParts (part_ids: Vec) -> super::Result, super::Error, >); - sails_rs::io_struct_impl!(ResetEquippables (part_id: u32) -> super::Result<(), super::Error, >); - sails_rs::io_struct_impl!(SetEquippablesToAll (part_id: u32) -> super::Result<(), super::Error, >); - sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> super::Result); + sails_rs::io_struct_impl!(AddEquippables (part_id: u32, collection_ids: Vec) -> Result<(u32, Vec, ), super::Error>); + sails_rs::io_struct_impl!(AddParts (parts: Vec<(u32, super::Part, )>) -> Result, super::Error>); + sails_rs::io_struct_impl!(RemoveEquippable (part_id: u32, collection_id: ActorId) -> Result<(u32, ActorId, ), super::Error>); + sails_rs::io_struct_impl!(RemoveParts (part_ids: Vec) -> Result, super::Error>); + sails_rs::io_struct_impl!(ResetEquippables (part_id: u32) -> Result<(), super::Error>); + sails_rs::io_struct_impl!(SetEquippablesToAll (part_id: u32) -> Result<(), super::Error>); + sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> Result); sails_rs::io_struct_impl!(Part (part_id: u32) -> super::Option); } -} - -#[cfg(feature = "mockall")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; -#[cfg(feature = "mockall")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = sails_rs::client::GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> sails_rs::client::PendingCall;fn add_parts (&mut self, parts: Vec<(u32, Part, )>) -> sails_rs::client::PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> sails_rs::client::PendingCall;fn reset_equippables (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn equippable (&self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn part (&self, part_id: u32) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "mockall")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub RmrkCatalog {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog::RmrkCatalog for RmrkCatalog { type Env = sails_rs::client::GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> sails_rs::client::PendingCall;fn add_parts (&mut self, parts: Vec<(u32, Part, )>) -> sails_rs::client::PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> sails_rs::client::PendingCall;fn reset_equippables (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn equippable (&self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn part (&self, part_id: u32) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index 5fa0a6027..b593fc093 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -1,6 +1,5 @@ use crate::{ - ctor_generators::*, helpers::generate_doc_comments, mock_generator::*, service_generators::*, - type_generators::*, + ctor_generators::*, helpers::generate_doc_comments, service_generators::*, type_generators::*, }; use convert_case::{Case, Casing}; use genco::prelude::*; @@ -11,7 +10,6 @@ use std::collections::{HashMap, HashSet}; pub(crate) struct RootGenerator<'ast> { tokens: Tokens, - mocks_tokens: Tokens, service_impl_tokens: Tokens, service_trait_tokens: Tokens, anonymous_service_name: &'ast str, @@ -34,7 +32,6 @@ impl<'ast> RootGenerator<'ast> { Self { anonymous_service_name, tokens: Tokens::new(), - mocks_tokens: Tokens::new(), service_impl_tokens: Tokens::new(), service_trait_tokens: Tokens::new(), mocks_feature_name, @@ -47,20 +44,12 @@ impl<'ast> RootGenerator<'ast> { } pub(crate) fn finalize(self, with_no_std: bool) -> String { - let mocks_tokens = if let Some(mocks_feature_name) = self.mocks_feature_name { + let extern_std = if let Some(mocks_feature_name) = self.mocks_feature_name { quote! { $['\n'] #[cfg(feature = $(quoted(mocks_feature_name)))] #[cfg(not(target_arch = "wasm32"))] extern crate std; - $['\n'] - #[cfg(feature = $(quoted(mocks_feature_name)))] - #[cfg(not(target_arch = "wasm32"))] - pub mod mockall { - use super::*; - use $(self.sails_path)::mockall::*; - $(self.mocks_tokens) - } } } else { Tokens::new() @@ -80,6 +69,8 @@ impl<'ast> RootGenerator<'ast> { let program_name = &self.anonymous_service_name.to_case(Case::Pascal); quote_in! { tokens => + $extern_std + pub struct $(program_name)Program; impl $(self.sails_path)::client::Program for $(program_name)Program {} @@ -95,8 +86,6 @@ impl<'ast> RootGenerator<'ast> { } $(self.tokens) - - $mocks_tokens }; let mut result = tokens.to_file_string().unwrap(); @@ -150,14 +139,15 @@ impl<'ast> Visitor<'ast> for RootGenerator<'ast> { ); } - let mut client_gen = - ServiceGenerator::new(service_name, self.sails_path, self.no_derive_traits); + let mut client_gen = ServiceGenerator::new( + service_name, + self.sails_path, + &self.external_types, + self.mocks_feature_name, + self.no_derive_traits, + ); client_gen.visit_service_unit(service); self.tokens.extend(client_gen.finalize()); - - let mut mock_gen = MockGenerator::new(service_name, self.sails_path); - mock_gen.visit_service_unit(service); - self.mocks_tokens.extend(mock_gen.finalize()); } fn visit_type(&mut self, t: &'ast ast::Type) { diff --git a/rs/client-gen/src/service_generators.rs b/rs/client-gen/src/service_generators.rs index 305c5f7f9..c9ddbaa73 100644 --- a/rs/client-gen/src/service_generators.rs +++ b/rs/client-gen/src/service_generators.rs @@ -1,21 +1,25 @@ +use crate::events_generator::EventsModuleGenerator; +use crate::helpers::*; +use crate::mock_generator::MockGenerator; +use crate::type_generators::{TopLevelTypeGenerator, generate_type_decl_with_path}; use convert_case::{Case, Casing}; use genco::prelude::*; use rust::Tokens; use sails_idl_parser_v2::{ast, visitor, visitor::Visitor}; - -use crate::events_generator::EventsModuleGenerator; -use crate::helpers::*; -use crate::type_generators::{TopLevelTypeGenerator, generate_type_decl_with_path}; +use std::collections::HashMap; /// Generates a service module with trait and struct implementation pub(crate) struct ServiceGenerator<'ast> { service_name: &'ast str, sails_path: &'ast str, + external_types: &'ast HashMap<&'ast str, &'ast str>, + mocks_feature_name: Option<&'ast str>, trait_tokens: Tokens, impl_tokens: Tokens, io_tokens: Tokens, events_tokens: Tokens, types_tokens: Tokens, + mocks_tokens: Tokens, no_derive_traits: bool, } @@ -23,22 +27,41 @@ impl<'ast> ServiceGenerator<'ast> { pub(crate) fn new( service_name: &'ast str, sails_path: &'ast str, + external_types: &'ast HashMap<&'ast str, &'ast str>, + mocks_feature_name: Option<&'ast str>, no_derive_traits: bool, ) -> Self { Self { service_name, sails_path, + external_types, + mocks_feature_name, trait_tokens: Tokens::new(), impl_tokens: Tokens::new(), io_tokens: Tokens::new(), events_tokens: Tokens::new(), types_tokens: Tokens::new(), + mocks_tokens: Tokens::new(), no_derive_traits, } } pub(crate) fn finalize(self) -> Tokens { let service_name_snake = &self.service_name.to_case(Case::Snake); + let mock_tokens = if let Some(mocks_feature_name) = self.mocks_feature_name { + quote! { + $['\n'] + #[cfg(feature = $(quoted(mocks_feature_name)))] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use $(self.sails_path)::mockall::*; + $(self.mocks_tokens) + } + } + } else { + quote!() + }; quote! { $['\n'] pub mod $service_name_snake { @@ -65,6 +88,8 @@ impl<'ast> ServiceGenerator<'ast> { } $(self.events_tokens) + + $(mock_tokens) } } } @@ -75,6 +100,10 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'ast> { fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) { visitor::accept_service_unit(service, self); + let mut mock_gen = MockGenerator::new(self.service_name, self.sails_path); + mock_gen.visit_service_unit(service); + self.mocks_tokens.extend(mock_gen.finalize()); + if !service.events.is_empty() { let mut events_mod_gen = EventsModuleGenerator::new(self.service_name, self.sails_path); events_mod_gen.visit_service_unit(service); @@ -83,6 +112,10 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'ast> { } fn visit_type(&mut self, t: &'ast ast::Type) { + if self.external_types.contains_key(t.name.as_str()) { + return; + } + let mut type_gen = TopLevelTypeGenerator::new(&t.name, self.sails_path, self.no_derive_traits); type_gen.visit_type(t); @@ -114,7 +147,7 @@ impl<'ast> Visitor<'ast> for ServiceGenerator<'ast> { }; let output_type_decl_code = if let Some(throws_type) = &func.throws { - let ok_type = generate_type_decl_with_path(&func.output, ""); + let ok_type = generate_type_decl_with_path(&func.output, "super"); let err_type = generate_type_decl_with_path(throws_type, "super"); format!("Result<{ok_type}, {err_type}>") } else { diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 21f99997d..c9670f586 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -5,6 +5,10 @@ expression: "gen_client(idl, \"Basic\")" // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct BasicProgram; impl sails_rs::client::Program for BasicProgram {} pub trait Basic { @@ -75,16 +79,12 @@ pub mod basic { sails_rs::io_struct_impl!(DoThis (p1: u32, p2: super::MyParam) -> u16); sails_rs::io_struct_impl!(DoThat (p1: (u8, u32, )) -> u8); } -} - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> sails_rs::client::PendingCall;fn do_that (&mut self, p1: (u8, u32, )) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub Basic {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl basic::Basic for Basic { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> sails_rs::client::PendingCall;fn do_that (&mut self, p1: (u8, u32, )) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap b/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap index f5e8badec..283896a87 100644 --- a/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap +++ b/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap @@ -5,6 +5,10 @@ expression: "gen_client(IDL, \"ComplexTypesProgram\")" // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct ComplexTypesProgramProgram; impl sails_rs::client::Program for ComplexTypesProgramProgram {} pub trait ComplexTypesProgram { @@ -209,6 +213,14 @@ pub mod my_complex_service { sails_rs::io_struct_impl!(ProcessGenericData (input_data: super::GenericData, list_of_generics: Vec>, optional_generic_result: super::Option, >) -> super::GenericData); sails_rs::io_struct_impl!(UpdateStatus (id: u64, new_status: super::ServiceStatus, metadata: super::Option) -> super::GenericResult); } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub MyComplexService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl my_complex_service::MyComplexService for MyComplexService { type Env = sails_rs::client::GstdEnv; fn initialize (&mut self, start_data: ProgramGlobalInfo, max_size: NonZeroU32) -> sails_rs::client::PendingCall;fn get_data (&mut self, key: String) -> sails_rs::client::PendingCall;fn get_info (&self, ) -> sails_rs::client::PendingCall;fn get_actor_ids (&self, count: u32) -> sails_rs::client::PendingCall;fn process_generic_data (&mut self, input_data: GenericData, list_of_generics: Vec>, optional_generic_result: Option, >) -> sails_rs::client::PendingCall;fn update_status (&mut self, id: u64, new_status: ServiceStatus, metadata: Option) -> sails_rs::client::PendingCall; } } + } } pub mod another_service { @@ -242,17 +254,12 @@ pub mod another_service { sails_rs::io_struct_impl!(Ping () -> String); sails_rs::io_struct_impl!(ProcessValues (data: Vec) -> super::Result<(), super::ErrorType, >); } -} -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub MyComplexService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl my_complex_service::MyComplexService for MyComplexService { type Env = sails_rs::client::GstdEnv; fn initialize (&mut self, start_data: ProgramGlobalInfo, max_size: NonZeroU32) -> sails_rs::client::PendingCall;fn get_data (&mut self, key: String) -> sails_rs::client::PendingCall;fn get_info (&self, ) -> sails_rs::client::PendingCall;fn get_actor_ids (&self, count: u32) -> sails_rs::client::PendingCall;fn process_generic_data (&mut self, input_data: GenericData, list_of_generics: Vec>, optional_generic_result: Option, >) -> sails_rs::client::PendingCall;fn update_status (&mut self, id: u64, new_status: ServiceStatus, metadata: Option) -> sails_rs::client::PendingCall; } } - mock! { pub AnotherService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl another_service::AnotherService for AnotherService { type Env = sails_rs::client::GstdEnv; fn ping (&mut self, ) -> sails_rs::client::PendingCall;fn process_values (&mut self, data: Vec) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub AnotherService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl another_service::AnotherService for AnotherService { type Env = sails_rs::client::GstdEnv; fn ping (&mut self, ) -> sails_rs::client::PendingCall;fn process_values (&mut self, data: Vec) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index 211184815..69e82b2fc 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -5,6 +5,10 @@ expression: "gen_client(idl, \"ServiceWithEvents\")" // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct ServiceWithEventsProgram; impl sails_rs::client::Program for ServiceWithEventsProgram {} pub trait ServiceWithEvents { @@ -81,16 +85,12 @@ pub mod service_with_events { type Event = ServiceWithEventsEvents; } } -} -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: U256, p2: MyParam) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub ServiceWithEvents {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl service_with_events::ServiceWithEvents for ServiceWithEvents { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: U256, p2: MyParam) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index 411a1ca63..670b25944 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -28,15 +28,6 @@ pub mod service { #[codec(crate = my_crate::sails::scale_codec)] #[scale_info(crate = my_crate::sails::scale_info)] #[reflect_hash(crate = my_crate::sails)] - pub struct MyParam { - pub f1: u32, - pub f2: Vec, - pub f3: Option<(u8, u32)>, - } - #[derive(Encode, Decode, TypeInfo, ReflectHash)] - #[codec(crate = my_crate::sails::scale_codec)] - #[scale_info(crate = my_crate::sails::scale_info)] - #[reflect_hash(crate = my_crate::sails)] pub enum MyParam2 { Variant1, Variant2(u32), diff --git a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap index 082fc04ac..e472d9dcc 100644 --- a/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap +++ b/rs/client-gen/tests/snapshots/generator__full_with_sails_path.snap @@ -276,7 +276,7 @@ pub mod canvas { use super::*; my_crate::sails::io_struct_impl!(ColorPoint (point: super::Point, color: super::Color) -> Result<(), super::ColorError>); my_crate::sails::io_struct_impl!(KillPoint (point: super::Point) -> Result); - my_crate::sails::io_struct_impl!(Points (offset: u32, len: u32) -> Result, PointStatus, )>, String>); + my_crate::sails::io_struct_impl!(Points (offset: u32, len: u32) -> Result, super::PointStatus, )>, String>); my_crate::sails::io_struct_impl!(PointStatus (point: super::Point) -> super::Option); } @@ -618,7 +618,7 @@ pub mod this_that { pub mod io { use super::*; - my_crate::sails::io_struct_impl!(DoThat (param: super::DoThatParam) -> Result<(ActorId, u32, ManyVariantsReply, ), (String, )>); + my_crate::sails::io_struct_impl!(DoThat (param: super::DoThatParam) -> Result<(ActorId, u32, super::ManyVariantsReply, ), (String, )>); my_crate::sails::io_struct_impl!(DoThis (p1: u32, p2: String, p3: (super::Option, u8, ), p4: super::TupleStruct) -> (String, u32, )); my_crate::sails::io_struct_impl!(Noop () -> ()); my_crate::sails::io_struct_impl!(That () -> Result); @@ -1103,7 +1103,7 @@ pub mod my_complex_service { pub mod io { use super::*; my_crate::sails::io_struct_impl!(Initialize (start_data: super::ProgramGlobalInfo, max_size: u32) -> Result<(), super::ProgramError>); - my_crate::sails::io_struct_impl!(GetData (key: String) -> Result); + my_crate::sails::io_struct_impl!(GetData (key: String) -> Result); my_crate::sails::io_struct_impl!(GetInfo () -> super::ProgramGlobalInfo); my_crate::sails::io_struct_impl!(GetActorIds (count: u32) -> Vec); my_crate::sails::io_struct_impl!(ProcessGenericData (input_data: super::GenericData, list_of_generics: Vec>, optional_generic_result: super::Option, >) -> super::GenericData); diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index d76e889d9..1d823d51b 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -5,6 +5,10 @@ expression: "gen_client(idl, \"Multiple\")" // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct MultipleProgram; impl sails_rs::client::Program for MultipleProgram {} pub trait Multiple { @@ -66,6 +70,14 @@ pub mod multiple { sails_rs::io_struct_impl!(DoThis (p1: u32, p2: super::MyParam) -> u16); sails_rs::io_struct_impl!(DoThat (p1: (u8, u32, )) -> u8); } + + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> sails_rs::client::PendingCall;fn do_that (&mut self, p1: (u8, u32, )) -> sails_rs::client::PendingCall; } } + } } pub mod named { @@ -86,17 +98,12 @@ pub mod named { use super::*; sails_rs::io_struct_impl!(That (p1: u32) -> String); } -} -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub Multiple {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl multiple::Multiple for Multiple { type Env = sails_rs::client::GstdEnv; fn do_this (&mut self, p1: u32, p2: MyParam) -> sails_rs::client::PendingCall;fn do_that (&mut self, p1: (u8, u32, )) -> sails_rs::client::PendingCall; } } - mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = sails_rs::client::GstdEnv; fn that (&self, p1: u32) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub Named {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl named::Named for Named { type Env = sails_rs::client::GstdEnv; fn that (&self, p1: u32) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index c2ccb0204..e19dfe451 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -5,6 +5,10 @@ expression: "gen_client(IDL, \"RmrkCatalog\")" // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct RmrkCatalogProgram; impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { @@ -190,16 +194,12 @@ pub mod rmrk_catalog_service { sails_rs::io_struct_impl!(Equippable (part_id: u32, collection_id: ActorId) -> super::Result); sails_rs::io_struct_impl!(Part (part_id: u32) -> super::Option); } -} - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub RmrkCatalogService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog_service::RmrkCatalogService for RmrkCatalogService { type Env = sails_rs::client::GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> sails_rs::client::PendingCall;fn add_parts (&mut self, parts: Vec<(u32, Part, )>) -> sails_rs::client::PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> sails_rs::client::PendingCall;fn reset_equippables (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn equippable (&self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn part (&self, part_id: u32) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub RmrkCatalogService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl rmrk_catalog_service::RmrkCatalogService for RmrkCatalogService { type Env = sails_rs::client::GstdEnv; fn add_equippables (&mut self, part_id: u32, collection_ids: Vec) -> sails_rs::client::PendingCall;fn add_parts (&mut self, parts: Vec<(u32, Part, )>) -> sails_rs::client::PendingCall;fn remove_equippable (&mut self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn remove_parts (&mut self, part_ids: Vec) -> sails_rs::client::PendingCall;fn reset_equippables (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn set_equippables_to_all (&mut self, part_id: u32) -> sails_rs::client::PendingCall;fn equippable (&self, part_id: u32, collection_id: ActorId) -> sails_rs::client::PendingCall;fn part (&self, part_id: u32) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/client-gen/tests/snapshots/generator__scope_resolution.snap b/rs/client-gen/tests/snapshots/generator__scope_resolution.snap index 36c82f3d5..52b38c88c 100644 --- a/rs/client-gen/tests/snapshots/generator__scope_resolution.snap +++ b/rs/client-gen/tests/snapshots/generator__scope_resolution.snap @@ -5,6 +5,10 @@ expression: "gen_client(IDL, \"MyProgram\")" // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; + +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; pub struct MyProgramProgram; impl sails_rs::client::Program for MyProgramProgram {} pub trait MyProgram { @@ -112,16 +116,12 @@ pub mod my_service { sails_rs::io_struct_impl!(AnotherAction (input: super::ServiceCommonType) -> u32); sails_rs::io_struct_impl!(UseProgramType (input: super::ProgramOnlyType) -> bool); } -} - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -pub mod mockall { - use super::*; - use sails_rs::mockall::*; - mock! { pub MyService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl my_service::MyService for MyService { type Env = sails_rs::client::GstdEnv; fn do_something (&mut self, input: CommonType) -> sails_rs::client::PendingCall;fn another_action (&mut self, input: ServiceCommonType) -> sails_rs::client::PendingCall;fn use_program_type (&mut self, input: ProgramOnlyType) -> sails_rs::client::PendingCall; } } + #[cfg(feature = "with_mocks")] + #[cfg(not(target_arch = "wasm32"))] + pub mod mockall { + use super::*; + use sails_rs::mockall::*; + mock! { pub MyService {} #[allow(refining_impl_trait)] #[allow(clippy::type_complexity)] impl my_service::MyService for MyService { type Env = sails_rs::client::GstdEnv; fn do_something (&mut self, input: CommonType) -> sails_rs::client::PendingCall;fn another_action (&mut self, input: ServiceCommonType) -> sails_rs::client::PendingCall;fn use_program_type (&mut self, input: ProgramOnlyType) -> sails_rs::client::PendingCall; } } + } } diff --git a/rs/idl-gen/Cargo.toml b/rs/idl-gen/Cargo.toml index daa120641..06ef484cb 100644 --- a/rs/idl-gen/Cargo.toml +++ b/rs/idl-gen/Cargo.toml @@ -23,4 +23,4 @@ quote.workspace = true [dev-dependencies] insta.workspace = true -sails-idl-parser.workspace = true +sails-idl-parser-v2.workspace = true diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 457177357..8515889c3 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -161,9 +161,7 @@ impl<'a> ServiceBuilder<'a> { pub fn build(self) -> Result> { let mut services = Vec::new(); let mut extends = Vec::new(); - for meta in self.meta.base_services() { - // TODO: add base service names to Meta trait - let name = "TodoBaseName"; + for (name, meta) in self.meta.base_services() { extends.push(name.to_string()); // TODO: dedup base services based on `interface_id` services.extend(ServiceBuilder::new(name, meta).build()?); diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index e7c54a5b6..4436110cf 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -247,7 +247,7 @@ impl ProgramMeta for TestProgramWithNonEmptyCtorsMeta { type ConstructorsMeta = NonEmptyCtorsMeta; const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("", AnyServiceMeta::new::)]; + &[("Test", AnyServiceMeta::new::)]; const ASYNC: bool = false; } @@ -270,14 +270,16 @@ fn program_idl_works_with_empty_ctors() { let mut idl = Vec::new(); program::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); insta::assert_snapshot!(generated_idl); - // let generated_idl_program = generated_idl_program.unwrap(); - // assert!(generated_idl_program.ctor().is_none()); - // assert_eq!(generated_idl_program.services().len(), 1); - // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - // assert_eq!(generated_idl_program.types().len(), 10); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + assert!(generated_idl_program.program.is_some()); + let program = generated_idl_program.program.unwrap(); + assert!(program.ctors.is_empty()); + assert_eq!(generated_idl_program.services.len(), 1); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 7); } #[test] @@ -285,14 +287,16 @@ fn program_idl_works_with_non_empty_ctors() { let mut idl = Vec::new(); program::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); - // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); insta::assert_snapshot!(generated_idl); - // let generated_idl_program = generated_idl_program.unwrap(); - // assert_eq!(generated_idl_program.ctor().unwrap().funcs().len(), 2); - // assert_eq!(generated_idl_program.services().len(), 1); - // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - // assert_eq!(generated_idl_program.types().len(), 10); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + assert!(generated_idl_program.program.is_some()); + let program = generated_idl_program.program.unwrap(); + assert_eq!(program.ctors.len(), 2); + assert_eq!(generated_idl_program.services.len(), 1); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 7); } #[test] @@ -300,17 +304,21 @@ fn program_idl_works_with_multiple_services() { let mut idl = Vec::new(); program::generate_idl::(&mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); + insta::assert_snapshot!(generated_idl); - // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - // let generated_idl_program = generated_idl_program.unwrap(); - // assert!(generated_idl_program.ctor().is_none()); - // assert_eq!(generated_idl_program.services().len(), 2); - // assert_eq!(generated_idl_program.services()[0].name(), ""); - // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - // assert_eq!(generated_idl_program.services()[1].name(), "SomeService"); - // assert_eq!(generated_idl_program.services()[1].funcs().len(), 4); - // assert_eq!(generated_idl_program.types().len(), 10); + let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + assert!(generated_idl_program.program.is_some()); + let program = generated_idl_program.program.unwrap(); + assert!(program.ctors.is_empty()); + assert_eq!(generated_idl_program.services.len(), 2); + assert_eq!(generated_idl_program.services[0].name, "Service"); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 7); + // TODO: dedup services by id + assert_eq!(generated_idl_program.services[1].name, "SomeService"); + assert_eq!(generated_idl_program.services[1].funcs.len(), 4); + assert_eq!(generated_idl_program.services[1].types.len(), 7); } #[test] @@ -320,12 +328,11 @@ fn service_idl_works_with_basics() { let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); - // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - // let generated_idl_program = generated_idl_program.unwrap(); - // assert!(generated_idl_program.ctor().is_none()); - // assert_eq!(generated_idl_program.services().len(), 1); - // assert_eq!(generated_idl_program.services()[0].funcs().len(), 4); - // assert_eq!(generated_idl_program.types().len(), 10); + let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + assert!(generated_idl_program.program.is_none()); + assert_eq!(generated_idl_program.services.len(), 1); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 7); } #[test] @@ -343,10 +350,13 @@ fn service_idl_works_with_base_services() { let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); - // let generated_idl_program = sails_idl_parser::ast::parse_idl(&generated_idl); - // let generated_idl_program = generated_idl_program.unwrap(); - // assert!(generated_idl_program.ctor().is_none()); - // assert_eq!(generated_idl_program.services().len(), 1); - // assert_eq!(generated_idl_program.services()[0].funcs().len(), 6); - // assert_eq!(generated_idl_program.types().len(), 10); + let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + assert!(generated_idl_program.program.is_none()); + assert_eq!(generated_idl_program.services.len(), 2); + assert_eq!(generated_idl_program.services[0].funcs.len(), 4); + assert_eq!(generated_idl_program.services[0].types.len(), 0); + assert_eq!(generated_idl_program.services[0].events.len(), 2); + + assert_eq!(generated_idl_program.services[1].funcs.len(), 4); + assert_eq!(generated_idl_program.services[1].types.len(), 7); } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap index 2a06a5179..de9372c5a 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap @@ -4,7 +4,7 @@ expression: generated_idl --- !@sails: 0.9.2 -service { +service Test { events { /// `This` Done ThisDone ( @@ -92,6 +92,6 @@ program ProgramToDo { FromStr(p1: String); } services { - , + Test, } } diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap index 823ba16e6..c6b2782a5 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -4,7 +4,7 @@ expression: generated_idl --- !@sails: 0.9.2 -service TodoBaseName { +service B { events { ThisDoneBase(u32), ThatDoneBase { @@ -23,7 +23,7 @@ service TodoBaseName { service ServiceToDo { extends { - TodoBaseName, + B, } events { /// `This` Done diff --git a/rs/src/builder.rs b/rs/src/builder.rs index fcedfa605..a7a412193 100644 --- a/rs/src/builder.rs +++ b/rs/src/builder.rs @@ -15,7 +15,7 @@ use std::{ /// See [Builder::build()]. /// /// Code -/// ```rust +/// ```rust,ignore /// use std::{env, path::PathBuf}; /// /// let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); From 215d504f0b241ff0259f42fba8163304f95be81a Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 9 Dec 2025 18:10:38 +0100 Subject: [PATCH 05/16] wip: type resolver w/ errors --- rs/idl-gen/src/builder.rs | 4 +- rs/idl-gen/src/generic_resolver.rs | 58 +----- rs/idl-gen/src/type_resolver.rs | 312 ++++++++++++++++++++++------- 3 files changed, 241 insertions(+), 133 deletions(-) diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 8515889c3..1e097c9d4 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -86,7 +86,7 @@ impl ProgramBuilder { let mut exclude = HashSet::new(); exclude.insert(self.ctors_type_id); exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)?); - let resolver = TypeResolver::from(&self.registry, exclude); + let resolver = TypeResolver::try_from(&self.registry, exclude)?; let ctors = self.ctor_funcs(&resolver)?; let services = self.services_expo; let types = resolver.into_types(); @@ -168,7 +168,7 @@ impl<'a> ServiceBuilder<'a> { } let exclude = HashSet::from_iter(self.exclude_type_ids()?); - let resolver = TypeResolver::from(&self.registry, exclude); + let resolver = TypeResolver::try_from(&self.registry, exclude)?; let commands = self.commands(&resolver)?; let queries = self.queries(&resolver)?; let events = self.events(&resolver)?; diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index 0148a0ca1..bd32cf01b 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -224,11 +224,9 @@ mod syn_resolver { #[cfg(test)] mod tests { - use crate::type_resolver::TypeResolver; - use super::*; + use crate::type_resolver::TypeResolver; use scale_info::{MetaType, PortableRegistry, Registry, TypeInfo}; - use std::collections::BTreeMap; #[allow(dead_code)] #[derive(TypeInfo)] @@ -236,55 +234,6 @@ mod tests { field: T, } - #[allow(dead_code)] - #[derive(TypeInfo)] - enum GenericEnum { - Variant1(T1), - Variant2(T2), - Variant3(T1, Option), - Variant4(Option<(T1, GenericStruct, u32)>), - } - - #[allow(dead_code)] - #[derive(TypeInfo)] - pub enum ManyVariants { - One, - Two(u32), - Three(Option>), - Four { a: u32, b: Option }, - Five(String, Vec), - Six((u32,)), - Seven(GenericEnum), - Eight([BTreeMap; 10]), - Nine(TupleVariantsDocs), - } - - #[derive(TypeInfo)] - pub enum TupleVariantsDocs { - /// Docs for no tuple docs 1 - NoTupleDocs1(u32, String), - NoTupleDocs2(gprimitives::CodeId, Vec), - /// Docs for tuple docs 1 - TupleDocs1( - u32, - /// This is the second field - String, - ), - TupleDocs2( - /// This is the first field - u32, - /// This is the second field - String, - ), - /// Docs for struct docs - StructDocs { - /// This is field `a` - a: u32, - /// This is field `b` - b: String, - }, - } - #[test] fn generic_resolver_struct_primitive() { use sails_idl_meta::{PrimitiveType::*, TypeDecl::*}; @@ -295,7 +244,7 @@ mod tests { let portable_registry = PortableRegistry::from(registry); let mut resolver = TypeResolver::from_registry(&portable_registry); let ty = portable_registry.resolve(id).unwrap(); - let type_params = resolver.resolve_type_params(ty); + let type_params = resolver.resolve_type_params(ty).unwrap(); let type_decl = resolver.get(id).unwrap(); @@ -314,8 +263,5 @@ mod tests { generics: vec![] }] })); - - // let string_struct = resolver.get(string_struct_id).unwrap(); - // assert_eq!(string_struct.to_string(), "GenericStruct"); } } diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index e65933c19..b13d9ecf4 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -1,5 +1,5 @@ +use super::*; use convert_case::{Case, Casing}; -use sails_idl_meta::*; use scale_info::{ Field, Path, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, TypeDefPrimitive, TypeDefVariant, form::PortableForm, @@ -28,17 +28,17 @@ impl UserDefinedEntry { impl<'a> TypeResolver<'a> { #[cfg(test)] pub fn from_registry(registry: &'a PortableRegistry) -> Self { - TypeResolver::from(registry, HashSet::new()) + TypeResolver::try_from(registry, HashSet::new()).unwrap() } - pub fn from(registry: &'a PortableRegistry, exclude: HashSet) -> Self { + pub fn try_from(registry: &'a PortableRegistry, exclude: HashSet) -> Result { let mut resolver = Self { registry, map: HashMap::new(), user_defined: HashMap::new(), }; - resolver.build_type_decl_map(exclude); - resolver + resolver.build_type_decl_map(exclude)?; + Ok(resolver) } pub fn into_types(self) -> Vec { @@ -55,7 +55,7 @@ impl<'a> TypeResolver<'a> { self.map.get(&key) } - fn build_type_decl_map(&mut self, exclude: HashSet) { + fn build_type_decl_map(&mut self, exclude: HashSet) -> Result<()> { let filtered: Vec<_> = self .registry .types @@ -63,72 +63,86 @@ impl<'a> TypeResolver<'a> { .filter(|pt| !exclude.contains(&pt.id)) .collect(); for pt in filtered { - let type_decl = self.resolve_type_decl(&pt.ty); + let type_decl = self.resolve_type_decl(&pt.ty)?; self.map.insert(pt.id, type_decl); } + Ok(()) } - fn resolve_by_id(&mut self, id: u32) -> TypeDecl { + fn resolve_by_id(&mut self, id: u32) -> Result { if let Some(decl) = self.get(id) { - return decl.clone(); + return Ok(decl.clone()); } - let ty = self.registry.resolve(id).unwrap(); - let type_decl = self.resolve_type_decl(ty); + let ty = self + .registry + .resolve(id) + .ok_or(Error::TypeIdIsUnknown(id))?; + let type_decl = self.resolve_type_decl(ty)?; self.map.insert(id, type_decl.clone()); - type_decl - } - - fn resolve_type_decl(&mut self, ty: &Type) -> TypeDecl { - match &ty.type_def { - TypeDef::Composite(type_def_composite) => self - .resolve_known_composite(ty, type_def_composite) - .unwrap_or_else(|| { - let name = self.register_user_defined(ty); - self.resolve_user_defined(name, ty) - }), - TypeDef::Variant(type_def_variant) => self - .resolve_known_enum(ty, type_def_variant) - .unwrap_or_else(|| { - let name = self.register_user_defined(ty); - self.resolve_user_defined(name, ty) - }), + Ok(type_decl) + } + + fn resolve_type_decl(&mut self, ty: &Type) -> Result { + let decl = match &ty.type_def { + TypeDef::Composite(type_def_composite) => { + if let Some(decl) = self.resolve_known_composite(ty, type_def_composite) { + decl + } else { + let name = self.register_user_defined(ty)?; + self.resolve_user_defined(name, ty)? + } + } + TypeDef::Variant(type_def_variant) => { + if let Some(decl) = self.resolve_known_enum(ty, type_def_variant) { + decl + } else { + let name = self.register_user_defined(ty)?; + self.resolve_user_defined(name, ty)? + } + } TypeDef::Sequence(type_def_sequence) => TypeDecl::Slice { - item: Box::new(self.resolve_by_id(type_def_sequence.type_param.id)), + item: Box::new(self.resolve_by_id(type_def_sequence.type_param.id)?), }, TypeDef::Array(type_def_array) => TypeDecl::Array { - item: Box::new(self.resolve_by_id(type_def_array.type_param.id)), + item: Box::new(self.resolve_by_id(type_def_array.type_param.id)?), len: type_def_array.len, }, TypeDef::Tuple(type_def_tuple) => { if type_def_tuple.fields.is_empty() { TypeDecl::Primitive(PrimitiveType::Void) } else { - TypeDecl::tuple( - type_def_tuple - .fields - .iter() - .map(|f| self.resolve_by_id(f.id)) - .collect(), - ) + let types = type_def_tuple + .fields + .iter() + .map(|f| self.resolve_by_id(f.id)) + .collect::>>()?; + TypeDecl::tuple(types) } } TypeDef::Primitive(type_def_primitive) => { - TypeDecl::Primitive(primitive_map(type_def_primitive)) + TypeDecl::Primitive(primitive_map(type_def_primitive)?) + } + TypeDef::Compact(_) => { + return Err(Error::TypeIsUnsupported( + "TypeDef::Compact is unsupported".to_string(), + )); } - TypeDef::Compact(_) => unimplemented!("TypeDef::Compact is unimplemented"), TypeDef::BitSequence(_) => { - unimplemented!("TypeDef::BitSequence is unimplemented") + return Err(Error::TypeIsUnsupported( + "TypeDef::BitSequence is unsupported".to_string(), + )); } - } + }; + Ok(decl) } - fn register_user_defined(&mut self, ty: &Type) -> String { + fn register_user_defined(&mut self, ty: &Type) -> Result { let name = match self.unique_type_name(ty) { Ok(name) => name, - Err(exist) => return exist, + Err(exist) => return Ok(exist), }; - let type_params = self.resolve_type_params(ty); + let type_params = self.resolve_type_params(ty)?; let def = match &ty.type_def { TypeDef::Composite(type_def_composite) => { @@ -136,7 +150,7 @@ impl<'a> TypeResolver<'a> { .fields .iter() .map(|f| self.resolve_field(f, &type_params)) - .collect(); + .collect::>>()?; sails_idl_meta::TypeDef::Struct(StructDef { fields }) } TypeDef::Variant(type_def_variant) => { @@ -148,16 +162,15 @@ impl<'a> TypeResolver<'a> { .fields .iter() .map(|f| self.resolve_field(f, &type_params)) - .collect(); - EnumVariant { + .collect::>>()?; + Ok(EnumVariant { name: v.name.to_string(), def: StructDef { fields }, docs: v.docs.iter().map(|d| d.to_string()).collect(), annotations: vec![], // ("index".to_string(), Some(v.index.to_string())) - } + }) }) - .collect(); - + .collect::>>()?; sails_idl_meta::TypeDef::Enum(EnumDef { variants }) } _ => unreachable!(), @@ -173,18 +186,22 @@ impl<'a> TypeResolver<'a> { let path = ty.path.clone(); self.user_defined .insert(name.clone(), UserDefinedEntry { meta_type, path }); - name + Ok(name) } pub(crate) fn resolve_type_params( &mut self, ty: &Type, - ) -> Vec { + ) -> Result> { ty.type_params .iter() - .map(|tp| sails_idl_meta::TypeParameter { - name: tp.name.to_string(), - ty: tp.ty.map(|ty| self.resolve_by_id(ty.id)), + .map(|tp| { + let ty = match tp.ty { + Some(ref inner) => Some(self.resolve_by_id(inner.id)?), + None => None, + }; + let name = tp.name.to_string(); + Ok(sails_idl_meta::TypeParameter { name, ty }) }) .collect() } @@ -204,21 +221,31 @@ impl<'a> TypeResolver<'a> { unreachable!(); } - fn resolve_user_defined(&mut self, name: String, ty: &Type) -> TypeDecl { + fn resolve_user_defined(&mut self, name: String, ty: &Type) -> Result { let generics = ty .type_params .iter() - .map(|tp| self.resolve_by_id(tp.ty.as_ref().unwrap().id)) - .collect(); - TypeDecl::Named { name, generics } + .map(|tp| { + self.resolve_by_id( + tp.ty + .as_ref() + .ok_or(Error::TypeIsUnsupported(format!( + "Generic type parameter is unknown: {}", + tp.name + )))? + .id, + ) + }) + .collect::>>()?; + Ok(TypeDecl::Named { name, generics }) } fn resolve_field( &mut self, field: &Field, type_params: &Vec, - ) -> StructField { - let resolved = self.resolve_by_id(field.ty.id); + ) -> Result { + let resolved = self.resolve_by_id(field.ty.id)?; let type_decl = if let Some(type_name) = field.type_name.as_ref() && &resolved.to_string() != type_name && !type_params.is_empty() @@ -227,12 +254,12 @@ impl<'a> TypeResolver<'a> { } else { resolved }; - StructField { + Ok(StructField { name: field.name.as_ref().map(|s| s.to_string()), type_decl, docs: field.docs.iter().map(|d| d.to_string()).collect(), annotations: vec![], - } + }) } fn resolve_known_composite( @@ -258,16 +285,16 @@ impl<'a> TypeResolver<'a> { } else if is_type::>(ty) && let [vec_tp] = ty.type_params.as_slice() && let Some(ty) = vec_tp.ty + && let Ok(ty) = self.resolve_by_id(ty.id) { - let ty = self.resolve_by_id(ty.id); Some(Slice { item: Box::new(ty) }) } else if is_type::>(ty) && let [key_tp, value_tp] = ty.type_params.as_slice() && let Some(key) = key_tp.ty && let Some(value) = value_tp.ty + && let Ok(key) = self.resolve_by_id(key.id) + && let Ok(value) = self.resolve_by_id(value.id) { - let key = self.resolve_by_id(key.id); - let value = self.resolve_by_id(value.id); Some(Slice { item: Box::new(TypeDecl::tuple(vec![key, value])), }) @@ -285,15 +312,15 @@ impl<'a> TypeResolver<'a> { && let [ok_var, err_var] = def.variants.as_slice() && let [ok] = ok_var.fields.as_slice() && let [err] = err_var.fields.as_slice() + && let Ok(ok) = self.resolve_by_id(ok.ty.id) + && let Ok(err) = self.resolve_by_id(err.ty.id) { - let ok = self.resolve_by_id(ok.ty.id); - let err = self.resolve_by_id(err.ty.id); Some(TypeDecl::result(ok, err)) } else if is_type::>(ty) && let [_, some_var] = def.variants.as_slice() && let [some] = some_var.fields.as_slice() + && let Ok(decl) = self.resolve_by_id(some.ty.id) { - let decl = self.resolve_by_id(some.ty.id); Some(TypeDecl::option(decl)) } else { None @@ -305,10 +332,10 @@ fn is_type(type_info: &Type) -> bool { T::type_info().path.segments == type_info.path.segments } -fn primitive_map(type_def_primitive: &TypeDefPrimitive) -> PrimitiveType { +fn primitive_map(type_def_primitive: &TypeDefPrimitive) -> Result { use PrimitiveType::*; - match type_def_primitive { + let p = match type_def_primitive { TypeDefPrimitive::Bool => Bool, TypeDefPrimitive::Char => Char, TypeDefPrimitive::Str => String, @@ -323,8 +350,13 @@ fn primitive_map(type_def_primitive: &TypeDefPrimitive) -> PrimitiveType { TypeDefPrimitive::I32 => I32, TypeDefPrimitive::I64 => I64, TypeDefPrimitive::I128 => I128, - TypeDefPrimitive::I256 => todo!(), - } + TypeDefPrimitive::I256 => { + return Err(Error::TypeIsUnsupported( + "TypeDefPrimitive::I256 is unsupported".to_string(), + )); + } + }; + Ok(p) } fn possible_names_by_path(ty: &Type) -> impl Iterator + '_ { @@ -338,7 +370,9 @@ fn possible_names_by_path(ty: &Type) -> impl Iterator>), @@ -376,8 +410,9 @@ mod tests { Nine(TupleVariantsDocs), } + #[allow(dead_code)] #[derive(TypeInfo)] - pub enum TupleVariantsDocs { + enum TupleVariantsDocs { /// Docs for no tuple docs 1 NoTupleDocs1(u32, String), NoTupleDocs2(gprimitives::CodeId, Vec), @@ -402,6 +437,32 @@ mod tests { }, } + #[allow(dead_code)] + mod mod_1 { + use super::*; + + #[derive(TypeInfo)] + pub struct T1 {} + + pub mod mod_2 { + use super::*; + + #[derive(TypeInfo)] + pub struct T2 {} + } + } + + #[allow(dead_code)] + mod mod_2 { + use super::*; + + #[derive(TypeInfo)] + pub struct T1 {} + + #[derive(TypeInfo)] + pub struct T2 {} + } + #[test] fn type_resolver_h160_h256() { let mut registry = Registry::new(); @@ -604,4 +665,105 @@ mod tests { let as_generic_param = resolver.get(generic_id).unwrap(); assert_eq!(as_generic_param.to_string(), "GenericStruct"); } + + #[test] + fn non_zero_types_name_resolution_works() { + type Test = ( + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, + NonZeroU256, + ); + let mut registry = Registry::new(); + let id = registry.register_type(&MetaType::new::()).id; + let generic_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let ty = resolver.get(id).unwrap(); + assert_eq!( + ty.to_string(), + "(NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroU256)" + ); + + let as_generic_param = resolver.get(generic_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + "GenericStruct<(NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroU256)>" + ); + } + + macro_rules! type_name_resolution_works { + ($primitive:ty) => { + let mut registry = Registry::new(); + let id = registry.register_type(&MetaType::new::<$primitive>()).id; + let generic_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + let ty = resolver.get(id).unwrap(); + + assert_eq!(ty.to_string(), stringify!($primitive)); + let as_generic_param = resolver.get(generic_id).unwrap(); + assert_eq!( + as_generic_param.to_string(), + format!("GenericStruct<{}>", stringify!($primitive)) + ); + }; + } + + #[test] + fn actor_id_type_name_resolution_works() { + use gprimitives::ActorId; + type_name_resolution_works!(ActorId); + } + + #[test] + fn message_id_type_name_resolution_works() { + use gprimitives::MessageId; + type_name_resolution_works!(MessageId); + } + + #[test] + fn code_id_type_name_resolution_works() { + use gprimitives::CodeId; + type_name_resolution_works!(CodeId); + } + + #[test] + fn type_name_minification_works_for_types_with_the_same_mod_depth() { + let mut registry = Registry::new(); + let t1_id = registry.register_type(&MetaType::new::()).id; + let t2_id = registry.register_type(&MetaType::new::()).id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let t1_name = resolver.get(t1_id).unwrap().to_string(); + assert_eq!(t1_name, "T1"); + + let t2_name = resolver.get(t2_id).unwrap().to_string(); + assert_eq!(t2_name, "Mod2T1"); + } + + #[test] + fn type_name_minification_works_for_types_with_different_mod_depth() { + let mut registry = Registry::new(); + let t1_id = registry + .register_type(&MetaType::new::()) + .id; + let t2_id = registry.register_type(&MetaType::new::()).id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let t1_name = resolver.get(t1_id).unwrap().to_string(); + assert_eq!(t1_name, "T2"); + + let t2_name = resolver.get(t2_id).unwrap().to_string(); + assert_eq!(t2_name, "Mod2T2"); + } } From 313e4919e4a1f2612d985dfa3c2b595cce6dc1ef Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 10 Dec 2025 15:28:06 +0100 Subject: [PATCH 06/16] wip: generic resolver returns Result --- rs/idl-gen/src/generic_resolver.rs | 42 ++++++++++++------------------ rs/idl-gen/src/type_resolver.rs | 2 +- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index bd32cf01b..f027de795 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -1,29 +1,23 @@ -use sails_idl_meta::*; +use super::*; use std::collections::HashSet; pub(crate) fn resolve_generic_type_decl( type_decl: &TypeDecl, type_name: &str, type_params: &Vec, -) -> TypeDecl { +) -> Result { let candidates = build_generic_candidates(type_decl, type_params); let syn_name = syn_resolver::try_resolve(type_name).map(|td| td.to_string()); let match_name = syn_name.unwrap_or_else(|| type_name.to_string()); - println!( - "type_decl: {:?}, type_name: {}, match_name: {}, candidates: {:?}", - type_decl.to_string(), - type_name, - match_name, - candidates - .iter() - .map(|td| td.to_string()) - .collect::>() - ); candidates .into_iter() .find(|td| td.to_string() == match_name) - .unwrap_or_else(|| panic!("Not Resolved {type_name}")) + .ok_or_else(|| { + Error::TypeIsUnsupported(format!( + "Generic type {type_name} not resolved from decl {type_decl}" + )) + }) } struct GenericCandidates<'a> { @@ -43,12 +37,8 @@ impl<'a> GenericCandidates<'a> { } fn push(&mut self, candidate: TypeDecl, f: impl Fn(TypeDecl) -> TypeDecl) { - for (td, name) in &self.type_params { - if td == &&candidate { - println!( - "type_params: {:?}, candidate {:?}, td: {:?}", - &self.type_params, candidate, td - ); + for &(td, name) in &self.type_params { + if td == &candidate { self.resolved.insert(f(generic_type_decl(name))); } } @@ -134,7 +124,6 @@ fn build_generic_candidates( } } }; - println!("type_decls_resolved {:?}", candidates.resolved); candidates.resolved } @@ -161,10 +150,13 @@ mod syn_resolver { use TypeDecl::*; match t { - Type::Array(TypeArray { elem, len, .. }) => Some(Array { - item: Box::new(finalize_syn(elem)?), - len: len.to_token_stream().to_string().parse::().unwrap(), - }), + Type::Array(TypeArray { elem, len, .. }) => { + let len = len.to_token_stream().to_string().parse::().ok()?; + Some(Array { + item: Box::new(finalize_syn(elem)?), + len, + }) + } Type::Slice(TypeSlice { elem, .. }) => Some(Slice { item: Box::new(finalize_syn(elem)?), }), @@ -175,7 +167,7 @@ mod syn_resolver { // No paren types in the final output. Only single value tuples Type::Paren(TypeParen { elem, .. }) => finalize_syn(elem), Type::Path(TypePath { path, .. }) => { - let last_segment = path.segments.last().unwrap(); + let last_segment = path.segments.last()?; let name = last_segment.ident.to_string(); let generics: Vec<_> = diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index b13d9ecf4..7c4a91815 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -250,7 +250,7 @@ impl<'a> TypeResolver<'a> { && &resolved.to_string() != type_name && !type_params.is_empty() { - crate::generic_resolver::resolve_generic_type_decl(&resolved, type_name, type_params) + crate::generic_resolver::resolve_generic_type_decl(&resolved, type_name, type_params)? } else { resolved }; From 99fdc499ec6e36596bbef53a10398503c2b93a6a Mon Sep 17 00:00:00 2001 From: vobradovich Date: Thu, 11 Dec 2025 18:11:13 +0100 Subject: [PATCH 07/16] wip: resolve type names for const generics --- rs/idl-gen/src/generic_resolver.rs | 78 ++++++++----- rs/idl-gen/src/type_resolver.rs | 108 +++++++++++++++--- rs/idl-gen/tests/generator.rs | 12 +- ...r__program_idl_works_with_empty_ctors.snap | 9 +- ...gram_idl_works_with_multiple_services.snap | 18 ++- ...rogram_idl_works_with_non_empty_ctors.snap | 9 +- ..._service_idl_works_with_base_services.snap | 9 +- ...erator__service_idl_works_with_basics.snap | 9 +- 8 files changed, 192 insertions(+), 60 deletions(-) diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index f027de795..92bf9b9c4 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -5,11 +5,27 @@ pub(crate) fn resolve_generic_type_decl( type_decl: &TypeDecl, type_name: &str, type_params: &Vec, -) -> Result { +) -> Result<(TypeDecl, Vec)> { + let (generic_decl, suffixes) = + syn_resolver::try_resolve(type_name, type_decl).ok_or_else(|| { + Error::TypeIsUnsupported(format!( + "Generic type {type_name} not resolved from decl {type_decl}" + )) + })?; + let match_name = generic_decl.to_string(); let candidates = build_generic_candidates(type_decl, type_params); - let syn_name = syn_resolver::try_resolve(type_name).map(|td| td.to_string()); - let match_name = syn_name.unwrap_or_else(|| type_name.to_string()); + println!( + "type_decl: {:?}, type_name: {}, match_name: {}, suffixes: {:?}, candidates: {:?}", + type_decl.to_string(), + type_name, + match_name, + &suffixes, + candidates + .iter() + .map(|td| td.to_string()) + .collect::>() + ); candidates .into_iter() .find(|td| td.to_string() == match_name) @@ -18,6 +34,7 @@ pub(crate) fn resolve_generic_type_decl( "Generic type {type_name} not resolved from decl {type_decl}" )) }) + .map(|td| (td, suffixes)) } struct GenericCandidates<'a> { @@ -139,33 +156,47 @@ mod syn_resolver { TypeSlice, TypeTuple, }; - pub(super) fn try_resolve(type_name: &str) -> Option { - syn::parse_str::(type_name) - .map(|syn_type| finalize_syn(&syn_type)) - .ok() - .flatten() + pub(super) fn try_resolve( + type_name: &str, + type_decl: &TypeDecl, + ) -> Option<(TypeDecl, Vec)> { + let syn_type = syn::parse_str::(type_name).ok()?; + let mut suffixes = Vec::new(); + syn_resolve(&syn_type, type_decl, &mut suffixes).map(|td| (td, suffixes)) } - fn finalize_syn(t: &Type) -> Option { + fn syn_resolve(t: &Type, type_decl: &TypeDecl, suffixes: &mut Vec) -> Option { use TypeDecl::*; match t { Type::Array(TypeArray { elem, len, .. }) => { - let len = len.to_token_stream().to_string().parse::().ok()?; + let len_str = len.to_token_stream().to_string(); + let len = if let Ok(len) = len_str.parse::() { + len + } else if let TypeDecl::Array { item: _, len } = type_decl { + suffixes.push(format!("{len_str}{len}")); + *len + } else { + return None; + }; + let item = syn_resolve(elem, type_decl, suffixes)?; Some(Array { - item: Box::new(finalize_syn(elem)?), + item: Box::new(item), len, }) } Type::Slice(TypeSlice { elem, .. }) => Some(Slice { - item: Box::new(finalize_syn(elem)?), + item: Box::new(syn_resolve(elem, type_decl, suffixes)?), }), Type::Tuple(TypeTuple { elems, .. }) => Some(Tuple { - types: elems.iter().filter_map(finalize_syn).collect(), + types: elems + .iter() + .filter_map(|t| syn_resolve(t, type_decl, suffixes)) + .collect(), }), - Type::Reference(TypeReference { elem, .. }) => finalize_syn(elem), + Type::Reference(TypeReference { elem, .. }) => syn_resolve(elem, type_decl, suffixes), // No paren types in the final output. Only single value tuples - Type::Paren(TypeParen { elem, .. }) => finalize_syn(elem), + Type::Paren(TypeParen { elem, .. }) => syn_resolve(elem, type_decl, suffixes), Type::Path(TypePath { path, .. }) => { let last_segment = path.segments.last()?; let name = last_segment.ident.to_string(); @@ -175,16 +206,19 @@ mod syn_resolver { syn_args .args .iter() - .filter_map(finalize_type_inner) + .filter_map(|arg| match arg { + GenericArgument::Type(t) => syn_resolve(t, type_decl, suffixes), + _ => None, + }) .collect() } else { vec![] }; match name.as_str() { "Vec" => { - if let [_] = generics.as_slice() { + if let [td] = generics.as_slice() { Some(Slice { - item: Box::new(Tuple { types: generics }), + item: Box::new(td.clone()), }) } else { Some(Named { name, generics }) @@ -205,13 +239,6 @@ mod syn_resolver { _ => None, } } - - fn finalize_type_inner(arg: &GenericArgument) -> Option { - match arg { - GenericArgument::Type(t) => finalize_syn(t), - _ => None, - } - } } #[cfg(test)] @@ -241,7 +268,6 @@ mod tests { let type_decl = resolver.get(id).unwrap(); let candidates = build_generic_candidates(type_decl, &type_params); - println!("{candidates:?}"); assert_eq!(2, candidates.len()); assert!(candidates.contains(&Named { diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index 7c4a91815..3f3c786b2 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -1,8 +1,8 @@ use super::*; use convert_case::{Case, Casing}; use scale_info::{ - Field, Path, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, - TypeDefPrimitive, TypeDefVariant, form::PortableForm, + Field, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, TypeDefPrimitive, + TypeDefVariant, form::PortableForm, }; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -16,12 +16,36 @@ pub struct TypeResolver<'a> { #[derive(Debug, Clone)] pub struct UserDefinedEntry { pub meta_type: sails_idl_meta::Type, - pub path: Path, + pub ty: Type, } impl UserDefinedEntry { - fn is_type(&self, type_info: &Type) -> bool { - self.path == type_info.path + fn is_path_equals(&self, type_info: &Type) -> bool { + self.ty.path == type_info.path + } + + fn is_fields_equal(&self, type_info: &Type) -> bool { + let fs1 = Self::fields(&self.ty); + let fs2 = Self::fields(type_info); + fs1 == fs2 + } + + fn fields(type_info: &Type) -> Vec { + match &type_info.type_def { + TypeDef::Composite(type_def_composite) => type_def_composite + .fields + .iter() + .map(|f| f.ty.id) + .collect::>(), + TypeDef::Variant(type_def_variant) => { + let mut fields = Vec::new(); + type_def_variant.variants.iter().for_each(|v| { + fields.extend(v.fields.iter().map(|f| f.ty.id)); + }); + fields + } + _ => unreachable!(), + } } } @@ -137,7 +161,7 @@ impl<'a> TypeResolver<'a> { } fn register_user_defined(&mut self, ty: &Type) -> Result { - let name = match self.unique_type_name(ty) { + let mut name = match self.unique_type_name(ty) { Ok(name) => name, Err(exist) => return Ok(exist), }; @@ -149,7 +173,7 @@ impl<'a> TypeResolver<'a> { let fields = type_def_composite .fields .iter() - .map(|f| self.resolve_field(f, &type_params)) + .map(|f| self.resolve_field(f, &type_params, &mut name)) .collect::>>()?; sails_idl_meta::TypeDef::Struct(StructDef { fields }) } @@ -161,7 +185,7 @@ impl<'a> TypeResolver<'a> { let fields = v .fields .iter() - .map(|f| self.resolve_field(f, &type_params)) + .map(|f| self.resolve_field(f, &type_params, &mut name)) .collect::>>()?; Ok(EnumVariant { name: v.name.to_string(), @@ -176,6 +200,10 @@ impl<'a> TypeResolver<'a> { _ => unreachable!(), }; + if self.user_defined.contains_key(&name) { + return Ok(name); + } + let meta_type = sails_idl_meta::Type { name: name.clone(), type_params, @@ -183,9 +211,13 @@ impl<'a> TypeResolver<'a> { docs: ty.docs.iter().map(|d| d.to_string()).collect(), annotations: vec![], //("rust_type".to_string(), Some(ty.path.to_string())) }; - let path = ty.path.clone(); - self.user_defined - .insert(name.clone(), UserDefinedEntry { meta_type, path }); + self.user_defined.insert( + name.clone(), + UserDefinedEntry { + meta_type, + ty: ty.clone(), + }, + ); Ok(name) } @@ -209,11 +241,13 @@ impl<'a> TypeResolver<'a> { fn unique_type_name(&self, ty: &Type) -> Result { for name in possible_names_by_path(ty) { if let Some(exists) = self.user_defined.get(&name) { - if exists.is_type(ty) { - // type already registered + if !exists.is_path_equals(ty) { + continue; + } else if exists.is_fields_equal(ty) { + // type with exact fields already registered return Err(name); } else { - continue; + return Ok(name); } } return Ok(name); @@ -244,13 +278,21 @@ impl<'a> TypeResolver<'a> { &mut self, field: &Field, type_params: &Vec, + name: &mut String, ) -> Result { let resolved = self.resolve_by_id(field.ty.id)?; let type_decl = if let Some(type_name) = field.type_name.as_ref() && &resolved.to_string() != type_name - && !type_params.is_empty() { - crate::generic_resolver::resolve_generic_type_decl(&resolved, type_name, type_params)? + let (td, suffixes) = crate::generic_resolver::resolve_generic_type_decl( + &resolved, + type_name, + type_params, + )?; + for suffix in suffixes { + name.push_str(suffix.as_str()); + } + td } else { resolved }; @@ -766,4 +808,38 @@ mod tests { let t2_name = resolver.get(t2_id).unwrap().to_string(); assert_eq!(t2_name, "Mod2T2"); } + + #[test] + fn generic_const_struct_type_name_resolution_works() { + let mut registry = Registry::new(); + let n8_id = registry + .register_type(&MetaType::new::>()) + .id; + let n8_id_2 = registry + .register_type(&MetaType::new::>()) + .id; + let n32_id = registry + .register_type(&MetaType::new::>()) + .id; + let n256_id = registry + .register_type(&MetaType::new::>()) + .id; + let n32u256_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + let n8_name = resolver.get(n8_id).unwrap().to_string(); + let n8_name_2 = resolver.get(n8_id_2).unwrap().to_string(); + let n32_name = resolver.get(n32_id).unwrap().to_string(); + let n256_name = resolver.get(n256_id).unwrap().to_string(); + let n32u256_name = resolver.get(n32u256_id).unwrap().to_string(); + + assert_eq!(n8_name, "GenericConstStructN8M12"); + assert_eq!(n8_name_2, "GenericConstStructN8M8"); + assert_eq!(n32_name, "GenericConstStructN32M8"); + assert_eq!(n256_name, "GenericConstStructN256M832"); + assert_eq!(n32u256_name, "GenericConstStructN32M8"); + } } diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index 4436110cf..600c489d8 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -279,7 +279,7 @@ fn program_idl_works_with_empty_ctors() { assert!(program.ctors.is_empty()); assert_eq!(generated_idl_program.services.len(), 1); assert_eq!(generated_idl_program.services[0].funcs.len(), 4); - assert_eq!(generated_idl_program.services[0].types.len(), 7); + assert_eq!(generated_idl_program.services[0].types.len(), 8); } #[test] @@ -296,7 +296,7 @@ fn program_idl_works_with_non_empty_ctors() { assert_eq!(program.ctors.len(), 2); assert_eq!(generated_idl_program.services.len(), 1); assert_eq!(generated_idl_program.services[0].funcs.len(), 4); - assert_eq!(generated_idl_program.services[0].types.len(), 7); + assert_eq!(generated_idl_program.services[0].types.len(), 8); } #[test] @@ -314,11 +314,11 @@ fn program_idl_works_with_multiple_services() { assert_eq!(generated_idl_program.services.len(), 2); assert_eq!(generated_idl_program.services[0].name, "Service"); assert_eq!(generated_idl_program.services[0].funcs.len(), 4); - assert_eq!(generated_idl_program.services[0].types.len(), 7); + assert_eq!(generated_idl_program.services[0].types.len(), 8); // TODO: dedup services by id assert_eq!(generated_idl_program.services[1].name, "SomeService"); assert_eq!(generated_idl_program.services[1].funcs.len(), 4); - assert_eq!(generated_idl_program.services[1].types.len(), 7); + assert_eq!(generated_idl_program.services[1].types.len(), 8); } #[test] @@ -332,7 +332,7 @@ fn service_idl_works_with_basics() { assert!(generated_idl_program.program.is_none()); assert_eq!(generated_idl_program.services.len(), 1); assert_eq!(generated_idl_program.services[0].funcs.len(), 4); - assert_eq!(generated_idl_program.services[0].types.len(), 7); + assert_eq!(generated_idl_program.services[0].types.len(), 8); } #[test] @@ -358,5 +358,5 @@ fn service_idl_works_with_base_services() { assert_eq!(generated_idl_program.services[0].events.len(), 2); assert_eq!(generated_idl_program.services[1].funcs.len(), 4); - assert_eq!(generated_idl_program.services[1].types.len(), 7); + assert_eq!(generated_idl_program.services[1].types.len(), 8); } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap index 8db4467cd..c1c90237c 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap @@ -25,7 +25,7 @@ service Service { } functions { /// Some description - DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; /// Some multiline description /// Second line /// Third line @@ -45,7 +45,12 @@ service Service { p3: ManyVariants, } /// GenericConstStruct docs - struct GenericConstStruct { + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { /// GenericStruct field `field` field: [u8; 8], } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap index ce2c266d2..62fbf587b 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap @@ -25,7 +25,7 @@ service Service { } functions { /// Some description - DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; /// Some multiline description /// Second line /// Third line @@ -45,7 +45,12 @@ service Service { p3: ManyVariants, } /// GenericConstStruct docs - struct GenericConstStruct { + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { /// GenericStruct field `field` field: [u8; 8], } @@ -104,7 +109,7 @@ service SomeService { } functions { /// Some description - DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; /// Some multiline description /// Second line /// Third line @@ -124,7 +129,12 @@ service SomeService { p3: ManyVariants, } /// GenericConstStruct docs - struct GenericConstStruct { + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { /// GenericStruct field `field` field: [u8; 8], } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap index de9372c5a..71d908516 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap @@ -25,7 +25,7 @@ service Test { } functions { /// Some description - DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; /// Some multiline description /// Second line /// Third line @@ -45,7 +45,12 @@ service Test { p3: ManyVariants, } /// GenericConstStruct docs - struct GenericConstStruct { + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { /// GenericStruct field `field` field: [u8; 8], } diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap index c6b2782a5..84416b32e 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -45,7 +45,7 @@ service ServiceToDo { } functions { /// Some description - DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; /// Some multiline description /// Second line /// Third line @@ -65,7 +65,12 @@ service ServiceToDo { p3: ManyVariants, } /// GenericConstStruct docs - struct GenericConstStruct { + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { /// GenericStruct field `field` field: [u8; 8], } diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap index 223cb0972..7dae1a492 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap @@ -25,7 +25,7 @@ service ServiceToDo { } functions { /// Some description - DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStruct, p8: GenericConstStruct) -> String; + DoThis(p1: u32, p2: String, p3: (Option, u8), p4: TupleStruct, p5: GenericStruct, p6: GenericStruct, p7: GenericConstStructN8, p8: GenericConstStructN32) -> String; /// Some multiline description /// Second line /// Third line @@ -45,7 +45,12 @@ service ServiceToDo { p3: ManyVariants, } /// GenericConstStruct docs - struct GenericConstStruct { + struct GenericConstStructN32 { + /// GenericStruct field `field` + field: [u8; 32], + } + /// GenericConstStruct docs + struct GenericConstStructN8 { /// GenericStruct field `field` field: [u8; 8], } From 03e6a920744617368e8ba15944e3d23ef3efe64f Mon Sep 17 00:00:00 2001 From: vobradovich Date: Sun, 14 Dec 2025 18:51:16 +0100 Subject: [PATCH 08/16] wip: rewrite generic resolver --- rs/idl-gen/src/generic_resolver.rs | 460 ++++++++++++++++------------- rs/idl-gen/src/type_resolver.rs | 35 ++- 2 files changed, 277 insertions(+), 218 deletions(-) diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index 92bf9b9c4..ddcd8434b 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -1,151 +1,26 @@ use super::*; -use std::collections::HashSet; +use std::collections::BTreeSet; pub(crate) fn resolve_generic_type_decl( type_decl: &TypeDecl, type_name: &str, - type_params: &Vec, -) -> Result<(TypeDecl, Vec)> { - let (generic_decl, suffixes) = - syn_resolver::try_resolve(type_name, type_decl).ok_or_else(|| { + type_params: &[sails_idl_meta::TypeParameter], +) -> Result<(TypeDecl, BTreeSet)> { + let (generic_decl, suffixes) = syn_resolver::try_resolve(type_name, type_decl, type_params) + .ok_or_else(|| { Error::TypeIsUnsupported(format!( "Generic type {type_name} not resolved from decl {type_decl}" )) })?; - let match_name = generic_decl.to_string(); - let candidates = build_generic_candidates(type_decl, type_params); - println!( - "type_decl: {:?}, type_name: {}, match_name: {}, suffixes: {:?}, candidates: {:?}", + "type_decl: {:?}, type_name: {}, generic_decl: {}, suffixes: {:?}", type_decl.to_string(), type_name, - match_name, + generic_decl, &suffixes, - candidates - .iter() - .map(|td| td.to_string()) - .collect::>() ); - candidates - .into_iter() - .find(|td| td.to_string() == match_name) - .ok_or_else(|| { - Error::TypeIsUnsupported(format!( - "Generic type {type_name} not resolved from decl {type_decl}" - )) - }) - .map(|td| (td, suffixes)) -} -struct GenericCandidates<'a> { - resolved: HashSet, - type_params: Vec<(&'a TypeDecl, &'a str)>, -} - -impl<'a> GenericCandidates<'a> { - fn new(type_params: &'a [sails_idl_meta::TypeParameter]) -> Self { - Self { - resolved: HashSet::new(), - type_params: type_params - .iter() - .filter_map(|tp| tp.ty.as_ref().map(|ty| (ty, tp.name.as_str()))) - .collect(), - } - } - - fn push(&mut self, candidate: TypeDecl, f: impl Fn(TypeDecl) -> TypeDecl) { - for &(td, name) in &self.type_params { - if td == &candidate { - self.resolved.insert(f(generic_type_decl(name))); - } - } - self.resolved.insert(f(candidate)); - } -} - -fn build_generic_candidates( - type_decl: &TypeDecl, - type_params: &Vec, -) -> HashSet { - let mut candidates = GenericCandidates::new(type_params); - // push `type_decl` as generic param to candidates - candidates.push(type_decl.clone(), |td| td); - match type_decl { - TypeDecl::Slice { item } => { - let decls = build_generic_candidates(item, type_params); - for item in decls { - candidates.push(item, |td| TypeDecl::Slice { item: Box::new(td) }); - } - } - TypeDecl::Array { item, len } => { - let decls = build_generic_candidates(item, type_params); - for item in decls { - candidates.push(item, |td| TypeDecl::Array { - item: Box::new(td), - len: *len, - }); - } - } - TypeDecl::Tuple { types } => { - for (idx, item) in types.iter().enumerate() { - let decls = build_generic_candidates(item, type_params); - let type_decls_resolved: Vec<_> = candidates - .resolved - .iter() - .filter_map(|td| match td { - TypeDecl::Tuple { types } => Some(types.clone()), - _ => None, - }) - .collect(); - for tds in type_decls_resolved { - for item in &decls { - candidates.push(item.clone(), |td| { - let mut types = tds.clone(); - types[idx] = td; - TypeDecl::Tuple { types } - }); - } - } - } - } - TypeDecl::Primitive(_) => { - // already pushed as `type_decl` - } - TypeDecl::Named { name, generics } => { - for (idx, item) in generics.iter().enumerate() { - let decls = build_generic_candidates(item, type_params); - let type_decls_resolved: Vec<_> = candidates - .resolved - .iter() - .filter_map(|td| match td { - TypeDecl::Named { - name: resolved_name, - generics, - } if resolved_name == name => Some(generics.clone()), - _ => None, - }) - .collect(); - - for tds in type_decls_resolved { - for item in &decls { - candidates.push(item.clone(), |td| { - let mut generics = tds.clone(); - generics[idx] = td; - TypeDecl::Named { - name: name.to_string(), - generics, - } - }); - } - } - } - } - }; - candidates.resolved -} - -fn generic_type_decl(name: &str) -> TypeDecl { - TypeDecl::named(name.to_string()) + Ok((generic_decl, suffixes)) } mod syn_resolver { @@ -159,86 +34,172 @@ mod syn_resolver { pub(super) fn try_resolve( type_name: &str, type_decl: &TypeDecl, - ) -> Option<(TypeDecl, Vec)> { + type_params: &[sails_idl_meta::TypeParameter], + ) -> Option<(TypeDecl, BTreeSet)> { let syn_type = syn::parse_str::(type_name).ok()?; - let mut suffixes = Vec::new(); - syn_resolve(&syn_type, type_decl, &mut suffixes).map(|td| (td, suffixes)) + let mut suffixes = BTreeSet::new(); + syn_resolve(&syn_type, type_decl, type_params, &mut suffixes).map(|td| (td, suffixes)) } - fn syn_resolve(t: &Type, type_decl: &TypeDecl, suffixes: &mut Vec) -> Option { + fn syn_resolve( + ty: &Type, + td: &TypeDecl, + type_params: &[sails_idl_meta::TypeParameter], + suffixes: &mut BTreeSet, + ) -> Option { use TypeDecl::*; - match t { - Type::Array(TypeArray { elem, len, .. }) => { - let len_str = len.to_token_stream().to_string(); + // println!( + // "syn_resolve_matched ty: {}, type_decl: {}, type_params: {:?}", + // ty.to_token_stream().to_string(), + // td, + // type_params + // ); + + match (ty, td) { + ( + Type::Array(TypeArray { + elem, + len: len_expr, + .. + }), + Array { item, len }, + ) => { + let len_str = len_expr.to_token_stream().to_string(); let len = if let Ok(len) = len_str.parse::() { len - } else if let TypeDecl::Array { item: _, len } = type_decl { - suffixes.push(format!("{len_str}{len}")); - *len } else { - return None; + suffixes.insert(format!("{len_str}{len}")); + *len }; - let item = syn_resolve(elem, type_decl, suffixes)?; + let item = syn_resolve(elem, item, type_params, suffixes)?; Some(Array { item: Box::new(item), len, }) } - Type::Slice(TypeSlice { elem, .. }) => Some(Slice { - item: Box::new(syn_resolve(elem, type_decl, suffixes)?), - }), - Type::Tuple(TypeTuple { elems, .. }) => Some(Tuple { - types: elems + (Type::Slice(TypeSlice { elem, .. }), Slice { item }) => { + let item = syn_resolve(elem, item, type_params, suffixes)?; + Some(Slice { + item: Box::new(item), + }) + } + (Type::Tuple(TypeTuple { elems, .. }), Tuple { types }) + if elems.len() == types.len() => + { + let types: Option> = elems .iter() - .filter_map(|t| syn_resolve(t, type_decl, suffixes)) - .collect(), - }), - Type::Reference(TypeReference { elem, .. }) => syn_resolve(elem, type_decl, suffixes), + .zip(types) + .map(|(ty, td)| syn_resolve(ty, td, type_params, suffixes)) + .collect(); + Some(Tuple { types: types? }) + } + (Type::Reference(TypeReference { elem, .. }), _) => { + syn_resolve(elem, td, type_params, suffixes) + } // No paren types in the final output. Only single value tuples - Type::Paren(TypeParen { elem, .. }) => syn_resolve(elem, type_decl, suffixes), - Type::Path(TypePath { path, .. }) => { + (Type::Paren(TypeParen { elem, .. }), _) => { + syn_resolve(elem, td, type_params, suffixes) + } + (Type::Path(TypePath { path, .. }), Primitive(_)) => { + if let Some(td) = generic_param(type_params, path) { + return Some(td); + } + + Some(td.clone()) + } + (Type::Path(TypePath { path, .. }), Slice { item }) => { + if let Some(td) = generic_param(type_params, path) { + return Some(td); + } + let last_segment = path.segments.last()?; - let name = last_segment.ident.to_string(); - - let generics: Vec<_> = - if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { - syn_args - .args - .iter() - .filter_map(|arg| match arg { - GenericArgument::Type(t) => syn_resolve(t, type_decl, suffixes), - _ => None, - }) - .collect() - } else { - vec![] - }; - match name.as_str() { - "Vec" => { - if let [td] = generics.as_slice() { - Some(Slice { - item: Box::new(td.clone()), - }) - } else { - Some(Named { name, generics }) + let ty_name = last_segment.ident.to_string(); + let mut ty_generics: Vec<&Type> = Vec::new(); + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + for arg in &syn_args.args { + match arg { + GenericArgument::Type(t) => ty_generics.push(t), + GenericArgument::Const(c) => { + println!("Const = {}", c.to_token_stream()) + } + _ => {} } } - "BTreeMap" => { - if let [_, _] = generics.as_slice() { - Some(Slice { - item: Box::new(Tuple { types: generics }), - }) - } else { - Some(Named { name, generics }) + } + if ty_name == "Vec" + && let [elem] = ty_generics.as_slice() + { + let item = syn_resolve(elem, item, type_params, suffixes)?; + return Some(Slice { + item: Box::new(item), + }); + } + if ty_name == "BTreeMap" + && let Tuple { types } = item.as_ref() + && let [key, value] = types.as_slice() + && let [ty_key, ty_value] = ty_generics.as_slice() + { + let key = syn_resolve(ty_key, key, type_params, suffixes)?; + let value = syn_resolve(ty_value, value, type_params, suffixes)?; + return Some(Slice { + item: Box::new(Tuple { + types: vec![key, value], + }), + }); + } + + None + } + (Type::Path(TypePath { path, .. }), Named { name, generics }) => { + if let Some(td) = generic_param(type_params, path) { + return Some(td); + } + + let last_segment = path.segments.last()?; + // let ty_name = last_segment.ident.to_string(); + let mut ty_generics: Vec<&Type> = Vec::new(); + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + for arg in &syn_args.args { + match arg { + GenericArgument::Type(t) => ty_generics.push(t), + GenericArgument::Const(c) => { + println!("Const = {}", c.to_token_stream()) + } + _ => {} } } - _ => Some(Named { name, generics }), } + let generics: Option> = ty_generics + .iter() + .zip(generics) + .map(|(ty, td)| syn_resolve(ty, td, type_params, suffixes)) + .collect(); + Some(Named { + name: name.clone(), + generics: generics?, + }) } + (Type::Path(TypePath { path, .. }), _) => generic_param(type_params, path), _ => None, } } + + fn generic_param( + type_params: &[sails_idl_meta::TypeParameter], + path: &syn::Path, + ) -> Option { + if let Some(ident) = path.get_ident() + && type_params.iter().any(|tp| *ident == tp.name) + { + Some(TypeDecl::Named { + name: ident.to_string(), + generics: vec![], + }) + } else { + None + } + } } #[cfg(test)] @@ -250,11 +211,26 @@ mod tests { #[allow(dead_code)] #[derive(TypeInfo)] struct GenericStruct { - field: T, + f1: T, + f2: Option, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ConstStruct { + f1: [u8; N], + f2: Option<[u8; N]>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ConstGenericStruct { + f1: GenericStruct, + f2: ConstStruct, } #[test] - fn generic_resolver_struct_primitive() { + fn generic_resolver_generic_struct() { use sails_idl_meta::{PrimitiveType::*, TypeDecl::*}; let meta_type = MetaType::new::>(); @@ -267,19 +243,99 @@ mod tests { let type_decl = resolver.get(id).unwrap(); - let candidates = build_generic_candidates(type_decl, &type_params); - - assert_eq!(2, candidates.len()); - assert!(candidates.contains(&Named { - name: "GenericStruct".to_string(), - generics: vec![Primitive(U32)] - })); - assert!(candidates.contains(&Named { - name: "GenericStruct".to_string(), - generics: vec![Named { - name: "T".to_string(), + let (generic_decl, _) = + resolve_generic_type_decl(type_decl, "GenericStruct", &type_params).unwrap(); + + assert_eq!( + &Named { + name: "GenericStruct".to_string(), + generics: vec![Primitive(U32)] + }, + type_decl + ); + + assert_eq!( + Named { + name: "GenericStruct".to_string(), + generics: vec![Named { + name: "T".to_string(), + generics: vec![] + }] + }, + generic_decl + ); + } + + #[test] + fn generic_resolver_cosnt_struct() { + use sails_idl_meta::TypeDecl::*; + + let meta_type = MetaType::new::>(); + let mut registry = Registry::new(); + let id = registry.register_type(&meta_type).id; + let portable_registry = PortableRegistry::from(registry); + let mut resolver = TypeResolver::from_registry(&portable_registry); + let ty = portable_registry.resolve(id).unwrap(); + let type_params = resolver.resolve_type_params(ty).unwrap(); + + let type_decl = resolver.get(id).unwrap(); + + let (generic_decl, _) = + resolve_generic_type_decl(type_decl, "ConstStruct", &type_params).unwrap(); + + assert_eq!( + &Named { + name: "ConstStructN32".to_string(), + generics: vec![] + }, + type_decl + ); + + assert_eq!( + Named { + name: "ConstStructN32".to_string(), generics: vec![] - }] - })); + }, + generic_decl + ); + } + + #[test] + fn generic_resolver_generic_cosnt_struct() { + use sails_idl_meta::{PrimitiveType::*, TypeDecl::*}; + + let meta_type_u8_32 = MetaType::new::>(); + let meta_type_u8_64 = MetaType::new::>(); + let mut registry = Registry::new(); + let u8_32_id = registry.register_type(&meta_type_u8_32).id; + let _u8_64_id = registry.register_type(&meta_type_u8_64).id; + let portable_registry = PortableRegistry::from(registry); + let mut resolver = TypeResolver::from_registry(&portable_registry); + let ty = portable_registry.resolve(u8_32_id).unwrap(); + let type_params = resolver.resolve_type_params(ty).unwrap(); + + let type_decl = resolver.get(u8_32_id).unwrap(); + + let (generic_decl, _) = + resolve_generic_type_decl(type_decl, "ConstGenericStruct", &type_params).unwrap(); + + assert_eq!( + &Named { + name: "ConstGenericStruct".to_string(), + generics: vec![Primitive(U8)] + }, + type_decl + ); + + assert_eq!( + Named { + name: "ConstGenericStruct".to_string(), + generics: vec![Named { + name: "T".to_string(), + generics: vec![] + }] + }, + generic_decl + ); } } diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index 3f3c786b2..91372fe9e 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -4,7 +4,7 @@ use scale_info::{ Field, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, TypeDefPrimitive, TypeDefVariant, form::PortableForm, }; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; #[derive(Debug, Clone)] pub struct TypeResolver<'a> { @@ -167,13 +167,14 @@ impl<'a> TypeResolver<'a> { }; let type_params = self.resolve_type_params(ty)?; + let mut suffixes = BTreeSet::new(); let def = match &ty.type_def { TypeDef::Composite(type_def_composite) => { let fields = type_def_composite .fields .iter() - .map(|f| self.resolve_field(f, &type_params, &mut name)) + .map(|f| self.resolve_field(f, &type_params, &mut suffixes)) .collect::>>()?; sails_idl_meta::TypeDef::Struct(StructDef { fields }) } @@ -185,7 +186,7 @@ impl<'a> TypeResolver<'a> { let fields = v .fields .iter() - .map(|f| self.resolve_field(f, &type_params, &mut name)) + .map(|f| self.resolve_field(f, &type_params, &mut suffixes)) .collect::>>()?; Ok(EnumVariant { name: v.name.to_string(), @@ -200,6 +201,10 @@ impl<'a> TypeResolver<'a> { _ => unreachable!(), }; + for suffix in suffixes { + name.push_str(suffix.as_str()); + } + if self.user_defined.contains_key(&name) { return Ok(name); } @@ -277,21 +282,19 @@ impl<'a> TypeResolver<'a> { fn resolve_field( &mut self, field: &Field, - type_params: &Vec, - name: &mut String, + type_params: &[sails_idl_meta::TypeParameter], + suffixes: &mut BTreeSet, ) -> Result { let resolved = self.resolve_by_id(field.ty.id)?; let type_decl = if let Some(type_name) = field.type_name.as_ref() && &resolved.to_string() != type_name { - let (td, suffixes) = crate::generic_resolver::resolve_generic_type_decl( + let (td, suf) = crate::generic_resolver::resolve_generic_type_decl( &resolved, type_name, type_params, )?; - for suffix in suffixes { - name.push_str(suffix.as_str()); - } + suffixes.extend(suf); td } else { resolved @@ -424,9 +427,9 @@ mod tests { #[allow(dead_code)] #[derive(TypeInfo)] - struct GenericConstStruct { + struct GenericConstStruct { field: [T; N], - field2: [T; M], + field2: Option<[T; O]>, } #[allow(dead_code)] @@ -836,10 +839,10 @@ mod tests { let n256_name = resolver.get(n256_id).unwrap().to_string(); let n32u256_name = resolver.get(n32u256_id).unwrap().to_string(); - assert_eq!(n8_name, "GenericConstStructN8M12"); - assert_eq!(n8_name_2, "GenericConstStructN8M8"); - assert_eq!(n32_name, "GenericConstStructN32M8"); - assert_eq!(n256_name, "GenericConstStructN256M832"); - assert_eq!(n32u256_name, "GenericConstStructN32M8"); + assert_eq!(n8_name, "GenericConstStructN8O12"); + assert_eq!(n8_name_2, "GenericConstStructN8O8"); + assert_eq!(n32_name, "GenericConstStructN32O8"); + assert_eq!(n256_name, "GenericConstStructN256O832"); + assert_eq!(n32u256_name, "GenericConstStructN32O8"); } } From f8b8912ca78065a4e4b5dacd7efe6de7aac0b484 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 15 Dec 2025 14:14:13 +0100 Subject: [PATCH 09/16] wip: refine idl-gen --- rs/idl-gen/src/errors.rs | 4 - rs/idl-gen/src/generic_resolver.rs | 159 ++++++++---------- rs/idl-gen/src/lib.rs | 49 +++--- rs/idl-gen/src/type_resolver.rs | 6 - rs/idl-gen/tests/generator.rs | 22 ++- ...r__program_idl_works_with_empty_ctors.snap | 2 +- ...gram_idl_works_with_multiple_services.snap | 2 +- ...rogram_idl_works_with_non_empty_ctors.snap | 2 +- ..._service_idl_works_with_base_services.snap | 2 +- ...erator__service_idl_works_with_basics.snap | 2 +- 10 files changed, 123 insertions(+), 127 deletions(-) diff --git a/rs/idl-gen/src/errors.rs b/rs/idl-gen/src/errors.rs index 9923c17b2..cb8df4b1b 100644 --- a/rs/idl-gen/src/errors.rs +++ b/rs/idl-gen/src/errors.rs @@ -12,10 +12,6 @@ pub enum Error { TypeIdIsUnknown(u32), #[error("type `{0}` is not supported")] TypeIsUnsupported(String), - // #[error(transparent)] - // TemplateIsBroken(#[from] Box), - // #[error(transparent)] - // RenderingFailed(#[from] Box), #[error(transparent)] Io(#[from] std::io::Error), } diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index ddcd8434b..b4839dfc2 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -12,13 +12,6 @@ pub(crate) fn resolve_generic_type_decl( "Generic type {type_name} not resolved from decl {type_decl}" )) })?; - println!( - "type_decl: {:?}, type_name: {}, generic_decl: {}, suffixes: {:?}", - type_decl.to_string(), - type_name, - generic_decl, - &suffixes, - ); Ok((generic_decl, suffixes)) } @@ -49,13 +42,6 @@ mod syn_resolver { ) -> Option { use TypeDecl::*; - // println!( - // "syn_resolve_matched ty: {}, type_decl: {}, type_params: {:?}", - // ty.to_token_stream().to_string(), - // td, - // type_params - // ); - match (ty, td) { ( Type::Array(TypeArray { @@ -65,10 +51,16 @@ mod syn_resolver { }), Array { item, len }, ) => { - let len_str = len_expr.to_token_stream().to_string(); - let len = if let Ok(len) = len_str.parse::() { + // const generic arguments support limited to Array type + let len = if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(lit_int), + .. + }) = len_expr + && let Ok(len) = lit_int.base10_parse::() + { len } else { + let len_str = len_expr.to_token_stream().to_string(); suffixes.insert(format!("{len_str}{len}")); *len }; @@ -101,86 +93,81 @@ mod syn_resolver { (Type::Paren(TypeParen { elem, .. }), _) => { syn_resolve(elem, td, type_params, suffixes) } - (Type::Path(TypePath { path, .. }), Primitive(_)) => { - if let Some(td) = generic_param(type_params, path) { - return Some(td); - } - - Some(td.clone()) - } - (Type::Path(TypePath { path, .. }), Slice { item }) => { - if let Some(td) = generic_param(type_params, path) { - return Some(td); + (Type::Path(TypePath { path, .. }), type_decl) => { + if let Some(generic) = generic_param(type_params, path) { + return Some(generic); } - let last_segment = path.segments.last()?; - let ty_name = last_segment.ident.to_string(); - let mut ty_generics: Vec<&Type> = Vec::new(); - if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { - for arg in &syn_args.args { - match arg { - GenericArgument::Type(t) => ty_generics.push(t), - GenericArgument::Const(c) => { - println!("Const = {}", c.to_token_stream()) + match type_decl { + Slice { item } => { + let last_segment = path.segments.last()?; + let ty_name = last_segment.ident.to_string(); + let mut ty_generics: Vec<&Type> = Vec::new(); + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + for arg in &syn_args.args { + if let GenericArgument::Type(t) = arg { + ty_generics.push(t) + } } - _ => {} } - } - } - if ty_name == "Vec" - && let [elem] = ty_generics.as_slice() - { - let item = syn_resolve(elem, item, type_params, suffixes)?; - return Some(Slice { - item: Box::new(item), - }); - } - if ty_name == "BTreeMap" - && let Tuple { types } = item.as_ref() - && let [key, value] = types.as_slice() - && let [ty_key, ty_value] = ty_generics.as_slice() - { - let key = syn_resolve(ty_key, key, type_params, suffixes)?; - let value = syn_resolve(ty_value, value, type_params, suffixes)?; - return Some(Slice { - item: Box::new(Tuple { - types: vec![key, value], - }), - }); - } - - None - } - (Type::Path(TypePath { path, .. }), Named { name, generics }) => { - if let Some(td) = generic_param(type_params, path) { - return Some(td); - } + if ty_name == "Vec" + && let [elem] = ty_generics.as_slice() + { + let item = syn_resolve(elem, item, type_params, suffixes)?; + return Some(Slice { + item: Box::new(item), + }); + } + if ty_name == "BTreeMap" + && let Tuple { types } = item.as_ref() + && let [key, value] = types.as_slice() + && let [ty_key, ty_value] = ty_generics.as_slice() + { + let key = syn_resolve(ty_key, key, type_params, suffixes)?; + let value = syn_resolve(ty_value, value, type_params, suffixes)?; + return Some(Slice { + item: Box::new(Tuple { + types: vec![key, value], + }), + }); + } - let last_segment = path.segments.last()?; - // let ty_name = last_segment.ident.to_string(); - let mut ty_generics: Vec<&Type> = Vec::new(); - if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { - for arg in &syn_args.args { - match arg { - GenericArgument::Type(t) => ty_generics.push(t), - GenericArgument::Const(c) => { - println!("Const = {}", c.to_token_stream()) + None + } + Array { item: _, len: _ } => None, + Tuple { types: _ } => None, + Named { name, generics } => { + let last_segment = path.segments.last()?; + // let ty_name = last_segment.ident.to_string(); + // TODO: const generic arguments are still ignored. + // We never add anything to the `suffixes` set when we see `GenericArgument::Const`, + // which means `TypeResolver::register_user_defined` (see rs/idl-gen/src/type_resolver.rs:163-207) + // cannot differentiate two instantiations of the same type that only differ by const parameters + // unless those consts appear inside an array length. + // For types such as `struct Tag;` or wrappers that only forward the const generic to another const generic, + // every instantiation (`Tag<1>`, `Tag<2>`, …) collapses to the same IDL name, + // so the second definition silently reuses the metadata of the first and the registry becomes inconsistent. + let mut ty_generics: Vec<&Type> = Vec::new(); + if let PathArguments::AngleBracketed(syn_args) = &last_segment.arguments { + for arg in &syn_args.args { + if let GenericArgument::Type(t) = arg { + ty_generics.push(t) + } } - _ => {} } + let generics: Option> = ty_generics + .iter() + .zip(generics) + .map(|(ty, td)| syn_resolve(ty, td, type_params, suffixes)) + .collect(); + Some(Named { + name: name.clone(), + generics: generics?, + }) } + Primitive(_) => Some(type_decl.clone()), } - let generics: Option> = ty_generics - .iter() - .zip(generics) - .map(|(ty, td)| syn_resolve(ty, td, type_params, suffixes)) - .collect(); - Some(Named { - name: name.clone(), - generics: generics?, - }) } - (Type::Path(TypePath { path, .. }), _) => generic_param(type_params, path), _ => None, } } diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index f419fdfc7..ba62a6fd4 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -16,15 +16,21 @@ pub mod program { use super::*; use sails_idl_meta::ProgramMeta; - pub fn generate_idl(mut idl_writer: impl Write) -> Result<()> { - let doc = build_program_ast::

(Some("ProgramToDo".to_string()))?; + pub fn generate_idl( + program_name: Option<&str>, + mut idl_writer: impl Write, + ) -> Result<()> { + let doc = build_program_ast::

(program_name)?; doc.write_into(&mut idl_writer)?; Ok(()) } - pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { + pub fn generate_idl_to_file( + program_name: Option<&str>, + path: impl AsRef, + ) -> Result<()> { let mut idl_new_content = Vec::new(); - generate_idl::

(&mut idl_new_content)?; + generate_idl::

(program_name, &mut idl_new_content)?; if let Ok(idl_old_content) = fs::read(&path) && idl_new_content == idl_old_content { @@ -41,15 +47,24 @@ pub mod service { use super::*; use sails_idl_meta::{AnyServiceMeta, ServiceMeta}; - pub fn generate_idl(mut idl_writer: impl Write) -> Result<()> { - let doc = build_service_ast("ServiceToDo", AnyServiceMeta::new::())?; + pub fn generate_idl( + service_name: &str, + mut idl_writer: impl Write, + ) -> Result<()> { + let doc = build_service_ast(service_name, AnyServiceMeta::new::())?; doc.write_into(&mut idl_writer)?; Ok(()) } pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { + let service_name = path + .as_ref() + .file_name() + .map(|f| f.to_string_lossy().to_string()) + .unwrap_or_else(|| "Service".to_string()); + let mut idl_new_content = Vec::new(); - generate_idl::(&mut idl_new_content)?; + generate_idl::(service_name.as_str(), &mut idl_new_content)?; if let Ok(idl_old_content) = fs::read(&path) && idl_new_content == idl_old_content { @@ -59,36 +74,28 @@ pub mod service { } } -fn build_program_ast(program_name: Option) -> Result { +fn build_program_ast(program_name: Option<&str>) -> Result { let mut services = Vec::new(); for (name, meta) in P::services() { services.extend(builder::ServiceBuilder::new(name, &meta).build()?); } let program = if let Some(name) = program_name { - Some(builder::ProgramBuilder::new::

().build(name)?) + Some(builder::ProgramBuilder::new::

().build(name.to_string())?) } else { None }; let doc = IdlDoc { - globals: vec![ - ("sails".to_string(), Some(SAILS_VERSION.to_string())), - // ("author".to_string(), Some(gen_meta_info.author)), - // ("version".to_string(), Some(gen_meta_info.version.format())), - ], + globals: vec![("sails".to_string(), Some(SAILS_VERSION.to_string()))], program, services, }; Ok(doc) } -fn build_service_ast(name: &'static str, meta: AnyServiceMeta) -> Result { - let services = builder::ServiceBuilder::new(name, &meta).build()?; +fn build_service_ast(service_name: &str, meta: AnyServiceMeta) -> Result { + let services = builder::ServiceBuilder::new(service_name, &meta).build()?; let doc = IdlDoc { - globals: vec![ - ("sails".to_string(), Some(SAILS_VERSION.to_string())), - // ("author".to_string(), Some(gen_meta_info.author)), - // ("version".to_string(), Some(gen_meta_info.version.format())), - ], + globals: vec![("sails".to_string(), Some(SAILS_VERSION.to_string()))], program: None, services, }; diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index 91372fe9e..a0a761ad3 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -528,7 +528,6 @@ mod tests { let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{resolver:#?}"); let h256_decl = resolver.get(h256_id).unwrap(); assert_eq!(*h256_decl, TypeDecl::Primitive(PrimitiveType::H256)); @@ -555,7 +554,6 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{resolver:#?}"); let u32_struct = resolver.get(u32_struct_id).unwrap(); assert_eq!(u32_struct.to_string(), "GenericStruct"); @@ -575,7 +573,6 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{resolver:#?}"); let u32_string_enum = resolver.get(u32_string_enum_id).unwrap(); assert_eq!(u32_string_enum.to_string(), "GenericEnum"); @@ -648,7 +645,6 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{resolver:#?}"); let u32_option = resolver.get(u32_option_id).unwrap(); assert_eq!(u32_option.to_string(), "Option"); @@ -683,7 +679,6 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - println!("{resolver:#?}"); let btree_map = resolver.get(btree_map_id).unwrap(); assert_eq!(btree_map.to_string(), "[(u32, String)]"); @@ -703,7 +698,6 @@ mod tests { .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); - // println!("{:#?}", resolver); let ty = resolver.get(id).unwrap(); assert_eq!(ty.to_string(), "ManyVariants"); diff --git a/rs/idl-gen/tests/generator.rs b/rs/idl-gen/tests/generator.rs index 600c489d8..371c927b1 100644 --- a/rs/idl-gen/tests/generator.rs +++ b/rs/idl-gen/tests/generator.rs @@ -268,7 +268,11 @@ impl ProgramMeta for TestProgramWithMultipleServicesMeta { #[test] fn program_idl_works_with_empty_ctors() { let mut idl = Vec::new(); - program::generate_idl::(&mut idl).unwrap(); + program::generate_idl::( + Some("TestProgramWithEmptyCtorsMeta"), + &mut idl, + ) + .unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); @@ -285,7 +289,11 @@ fn program_idl_works_with_empty_ctors() { #[test] fn program_idl_works_with_non_empty_ctors() { let mut idl = Vec::new(); - program::generate_idl::(&mut idl).unwrap(); + program::generate_idl::( + Some("TestProgramWithNonEmptyCtorsMeta"), + &mut idl, + ) + .unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); @@ -302,7 +310,11 @@ fn program_idl_works_with_non_empty_ctors() { #[test] fn program_idl_works_with_multiple_services() { let mut idl = Vec::new(); - program::generate_idl::(&mut idl).unwrap(); + program::generate_idl::( + Some("TestProgramWithMultipleServicesMeta"), + &mut idl, + ) + .unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); @@ -324,7 +336,7 @@ fn program_idl_works_with_multiple_services() { #[test] fn service_idl_works_with_basics() { let mut idl = Vec::new(); - service::generate_idl::(&mut idl).unwrap(); + service::generate_idl::("TestServiceMeta", &mut idl).unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); @@ -345,7 +357,7 @@ fn service_idl_works_with_base_services() { EventsMeta, ServiceMeta, >, - >(&mut idl) + >("ServiceMetaWithBase", &mut idl) .unwrap(); let generated_idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(generated_idl); diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap index c1c90237c..8e3ff2ffc 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_empty_ctors.snap @@ -88,7 +88,7 @@ service Service { } } -program ProgramToDo { +program TestProgramWithEmptyCtorsMeta { services { Service, } diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap index 62fbf587b..b7dd8e876 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap @@ -172,7 +172,7 @@ service SomeService { } } -program ProgramToDo { +program TestProgramWithMultipleServicesMeta { services { Service, SomeService, diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap index 71d908516..e19eb2ca2 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_non_empty_ctors.snap @@ -88,7 +88,7 @@ service Test { } } -program ProgramToDo { +program TestProgramWithNonEmptyCtorsMeta { constructors { /// This is New constructor New(); diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap index 84416b32e..6d8582cb9 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_base_services.snap @@ -21,7 +21,7 @@ service B { } } -service ServiceToDo { +service ServiceMetaWithBase { extends { B, } diff --git a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap index 7dae1a492..122912663 100644 --- a/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap +++ b/rs/idl-gen/tests/snapshots/generator__service_idl_works_with_basics.snap @@ -4,7 +4,7 @@ expression: generated_idl --- !@sails: 0.9.2 -service ServiceToDo { +service TestServiceMeta { events { /// `This` Done ThisDone ( From 06341cbb1ddee88e50e082a721b294b1d3d5c61c Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 15 Dec 2025 17:33:58 +0100 Subject: [PATCH 10/16] fix: client-gen, builder, benchmarks --- Cargo.lock | 1 + benchmarks/bench_data.json | 2 +- benchmarks/idls/alloc_stress_program.idl | 3 +- benchmarks/idls/compute_stress_program.idl | 12 +- benchmarks/ping-pong/client/Cargo.toml | 3 +- benchmarks/ping-pong/client/build.rs | 8 +- benchmarks/ping-pong/client/ping_pong.idl | 2 +- benchmarks/src/alloc_stress_program.rs | 20 +- benchmarks/src/benchmarks.rs | 21 +- benchmarks/src/compute_stress_program.rs | 35 +- benchmarks/src/counter_bench_program.rs | 20 +- examples/demo/client/demo_client.idl | 2 +- examples/demo/client/src/demo_client.rs | 7 +- examples/no-svcs-prog/wasm/no_svcs_prog.idl | 2 +- examples/proxy/src/main-idl-gen.rs | 11 +- examples/redirect/client/redirect_client.idl | 2 +- .../proxy-client/redirect_proxy_client.idl | 2 +- examples/rmrk/catalog/wasm/rmrk-catalog.idl | 2 +- .../rmrk/resource/app/src/rmrk_catalog.rs | 5 +- rs/client-gen/src/ctor_generators.rs | 14 +- rs/client-gen/src/lib.rs | 26 +- rs/client-gen/src/root_generator.rs | 52 +- rs/client-gen/tests/generator.rs | 20 +- .../snapshots/generator__basic_works.snap | 15 +- ...erator__complex_type_generation_works.snap | 7 +- .../snapshots/generator__events_works.snap | 17 +- .../snapshots/generator__external_types.snap | 10 - .../generator__multiple_services.snap | 15 +- .../snapshots/generator__rmrk_works.snap | 7 +- .../generator__scope_resolution.snap | 7 +- rs/ethexe/ethapp_with_events/tests/insta.rs | 2 +- ...nsta__ethapp_with_events_generate_idl.snap | 2 +- rs/idl-gen/src/meta.rs | 372 ------ rs/idl-gen/src/type_names.rs | 1074 ----------------- rs/src/builder.rs | 21 +- 35 files changed, 150 insertions(+), 1671 deletions(-) delete mode 100644 rs/idl-gen/src/meta.rs delete mode 100644 rs/idl-gen/src/type_names.rs diff --git a/Cargo.lock b/Cargo.lock index f17b40f53..b2f077d94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6183,6 +6183,7 @@ version = "0.9.2" dependencies = [ "ping-pong-bench-app", "sails-client-gen", + "sails-idl-gen", ] [[package]] diff --git a/benchmarks/bench_data.json b/benchmarks/bench_data.json index 47b95214f..0d0f7b6d4 100644 --- a/benchmarks/bench_data.json +++ b/benchmarks/bench_data.json @@ -1,6 +1,6 @@ { "compute": { - "median": 450514040279 + "median": 450514036959 }, "alloc": { "0": 564376739, diff --git a/benchmarks/idls/alloc_stress_program.idl b/benchmarks/idls/alloc_stress_program.idl index 208c0ce93..40f148273 100644 --- a/benchmarks/idls/alloc_stress_program.idl +++ b/benchmarks/idls/alloc_stress_program.idl @@ -1,4 +1,3 @@ - !@sails: 0.9.2 service AllocStress { @@ -12,7 +11,7 @@ service AllocStress { } } -program ProgramToDo { +program AllocStress { constructors { NewForBench(); } diff --git a/benchmarks/idls/compute_stress_program.idl b/benchmarks/idls/compute_stress_program.idl index 8d271b317..1e62c183f 100644 --- a/benchmarks/idls/compute_stress_program.idl +++ b/benchmarks/idls/compute_stress_program.idl @@ -5,15 +5,15 @@ program ComputeStress { services { ComputeStress, } - types { - struct ComputeStressResult { - res: u32, - } - } } service ComputeStress { functions { ComputeStress(n: u32) -> ComputeStressResult; } -} \ No newline at end of file + types { + struct ComputeStressResult { + res: u32, + } + } +} diff --git a/benchmarks/ping-pong/client/Cargo.toml b/benchmarks/ping-pong/client/Cargo.toml index dba7deff1..cfaedb042 100644 --- a/benchmarks/ping-pong/client/Cargo.toml +++ b/benchmarks/ping-pong/client/Cargo.toml @@ -9,5 +9,4 @@ repository.workspace = true [build-dependencies] ping-pong-bench-app = { path = "../app" } sails-client-gen.workspace = true -# TODO: Switch to new IDL generator crate -# sails-idl-gen.workspace = true +sails-idl-gen.workspace = true diff --git a/benchmarks/ping-pong/client/build.rs b/benchmarks/ping-pong/client/build.rs index ed2609c9b..2937308dd 100644 --- a/benchmarks/ping-pong/client/build.rs +++ b/benchmarks/ping-pong/client/build.rs @@ -1,12 +1,12 @@ -// use ping_pong_bench_app::PingPongProgram; +use ping_pong_bench_app::PingPongProgram; fn main() { let idl_file_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) .join("ping_pong.idl"); - // Generate IDL file for the `AllocStress` app - // TODO: uncomment - // sails_idl_gen::generate_idl_to_file::(&idl_file_path).unwrap(); + // Generate IDL file for the `PingPongProgram` app + sails_idl_gen::generate_idl_to_file::(Some("PingPong"), &idl_file_path) + .unwrap(); // Generate client code from IDL file sails_client_gen::ClientGenerator::from_idl_path(&idl_file_path) diff --git a/benchmarks/ping-pong/client/ping_pong.idl b/benchmarks/ping-pong/client/ping_pong.idl index 67fbde397..da5eb8d78 100644 --- a/benchmarks/ping-pong/client/ping_pong.idl +++ b/benchmarks/ping-pong/client/ping_pong.idl @@ -15,7 +15,7 @@ service PingPongService { } } -program ProgramToDo { +program PingPong { constructors { NewForBench(); } diff --git a/benchmarks/src/alloc_stress_program.rs b/benchmarks/src/alloc_stress_program.rs index b239a4f31..8413a96a7 100644 --- a/benchmarks/src/alloc_stress_program.rs +++ b/benchmarks/src/alloc_stress_program.rs @@ -1,33 +1,31 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; -pub struct AllocStressProgramProgram; -impl sails_rs::client::Program for AllocStressProgramProgram {} -pub trait AllocStressProgram { +pub struct AllocStressProgram; +impl sails_rs::client::Program for AllocStressProgram {} +pub trait AllocStress { type Env: sails_rs::client::GearEnv; fn alloc_stress(&self) -> sails_rs::client::Service; } -impl AllocStressProgram - for sails_rs::client::Actor -{ +impl AllocStress for sails_rs::client::Actor { type Env = E; fn alloc_stress(&self) -> sails_rs::client::Service { self.service(stringify!(AllocStress)) } } -pub trait AllocStressProgramCtors { +pub trait AllocStressCtors { type Env: sails_rs::client::GearEnv; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl AllocStressProgramCtors - for sails_rs::client::Deployment +impl AllocStressCtors + for sails_rs::client::Deployment { type Env = E; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } diff --git a/benchmarks/src/benchmarks.rs b/benchmarks/src/benchmarks.rs index 48d86aa57..e9da1f802 100644 --- a/benchmarks/src/benchmarks.rs +++ b/benchmarks/src/benchmarks.rs @@ -18,9 +18,9 @@ //! ``` use crate::clients::{ - alloc_stress_client::{AllocStressProgram, AllocStressProgramCtors, alloc_stress::*}, - compute_stress_client::{ComputeStressProgram, ComputeStressProgramCtors, compute_stress::*}, - counter_bench_client::{CounterBenchProgram, CounterBenchProgramCtors, counter_bench::*}, + alloc_stress_client::{AllocStress as _, AllocStressCtors, alloc_stress::*}, + compute_stress_client::{ComputeStress as _, ComputeStressCtors, compute_stress::*}, + counter_bench_client::{CounterBench as _, CounterBenchCtors, counter_bench::*}, }; use gtest::{System, constants::DEFAULT_USER_ALICE}; use itertools::{Either, Itertools}; @@ -57,10 +57,7 @@ async fn alloc_stress_bench() { async fn compute_stress_bench() { let wasm_path = "../target/wasm32-gear/release/compute_stress.opt.wasm"; let env = create_env(); - let program = deploy_for_bench(&env, wasm_path, |d| { - ComputeStressProgramCtors::new_for_bench(d) - }) - .await; + let program = deploy_for_bench(&env, wasm_path, |d| ComputeStressCtors::new_for_bench(d)).await; let mut service = program.compute_stress(); let input_value = 30; @@ -91,10 +88,7 @@ async fn compute_stress_bench() { async fn counter_bench() { let wasm_path = "../target/wasm32-gear/release/counter_bench.opt.wasm"; let env = create_env(); - let program = deploy_for_bench(&env, wasm_path, |d| { - CounterBenchProgramCtors::new_for_bench(d) - }) - .await; + let program = deploy_for_bench(&env, wasm_path, |d| CounterBenchCtors::new_for_bench(d)).await; let mut service = program.counter_bench(); let mut expected_value = 0; @@ -270,10 +264,7 @@ async fn alloc_stress_test(n: u32) -> (usize, u64) { // Path taken from the .binpath file let wasm_path = "../target/wasm32-gear/release/alloc_stress.opt.wasm"; let env = create_env(); - let program = deploy_for_bench(&env, wasm_path, |d| { - AllocStressProgramCtors::new_for_bench(d) - }) - .await; + let program = deploy_for_bench(&env, wasm_path, |d| AllocStressCtors::new_for_bench(d)).await; let mut service = program.alloc_stress(); let message_id = service.alloc_stress(n).send_one_way().unwrap(); diff --git a/benchmarks/src/compute_stress_program.rs b/benchmarks/src/compute_stress_program.rs index dbf759422..0dab756e6 100644 --- a/benchmarks/src/compute_stress_program.rs +++ b/benchmarks/src/compute_stress_program.rs @@ -1,16 +1,16 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; -pub struct ComputeStressProgramProgram; -impl sails_rs::client::Program for ComputeStressProgramProgram {} -pub trait ComputeStressProgram { +pub struct ComputeStressProgram; +impl sails_rs::client::Program for ComputeStressProgram {} +pub trait ComputeStress { type Env: sails_rs::client::GearEnv; fn compute_stress( &self, ) -> sails_rs::client::Service; } -impl ComputeStressProgram - for sails_rs::client::Actor +impl ComputeStress + for sails_rs::client::Actor { type Env = E; fn compute_stress( @@ -19,20 +19,19 @@ impl ComputeStressProgram self.service(stringify!(ComputeStress)) } } -pub trait ComputeStressProgramCtors { +pub trait ComputeStressCtors { type Env: sails_rs::client::GearEnv; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl ComputeStressProgramCtors - for sails_rs::client::Deployment +impl ComputeStressCtors + for sails_rs::client::Deployment { type Env = E; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor - { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } @@ -41,16 +40,16 @@ pub mod io { use super::*; sails_rs::io_struct_impl!(NewForBench () -> ()); } -#[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] -#[codec(crate = sails_rs::scale_codec)] -#[scale_info(crate = sails_rs::scale_info)] -#[reflect_hash(crate = sails_rs)] -pub struct ComputeStressResult { - pub res: u32, -} pub mod compute_stress { use super::*; + #[derive(PartialEq, Clone, Debug, Encode, Decode, TypeInfo, ReflectHash)] + #[codec(crate = sails_rs::scale_codec)] + #[scale_info(crate = sails_rs::scale_info)] + #[reflect_hash(crate = sails_rs)] + pub struct ComputeStressResult { + pub res: u32, + } pub trait ComputeStress { type Env: sails_rs::client::GearEnv; fn compute_stress( diff --git a/benchmarks/src/counter_bench_program.rs b/benchmarks/src/counter_bench_program.rs index 632002229..50560441e 100644 --- a/benchmarks/src/counter_bench_program.rs +++ b/benchmarks/src/counter_bench_program.rs @@ -1,16 +1,16 @@ // Code generated by sails-client-gen. DO NOT EDIT. #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; -pub struct CounterBenchProgramProgram; -impl sails_rs::client::Program for CounterBenchProgramProgram {} -pub trait CounterBenchProgram { +pub struct CounterBenchProgram; +impl sails_rs::client::Program for CounterBenchProgram {} +pub trait CounterBench { type Env: sails_rs::client::GearEnv; fn counter_bench( &self, ) -> sails_rs::client::Service; } -impl CounterBenchProgram - for sails_rs::client::Actor +impl CounterBench + for sails_rs::client::Actor { type Env = E; fn counter_bench( @@ -19,19 +19,19 @@ impl CounterBenchProgram self.service(stringify!(CounterBench)) } } -pub trait CounterBenchProgramCtors { +pub trait CounterBenchCtors { type Env: sails_rs::client::GearEnv; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor; + ) -> sails_rs::client::PendingCtor; } -impl CounterBenchProgramCtors - for sails_rs::client::Deployment +impl CounterBenchCtors + for sails_rs::client::Deployment { type Env = E; fn new_for_bench( self, - ) -> sails_rs::client::PendingCtor { + ) -> sails_rs::client::PendingCtor { self.pending_ctor(()) } } diff --git a/examples/demo/client/demo_client.idl b/examples/demo/client/demo_client.idl index f3a6baf60..fdd7e0923 100644 --- a/examples/demo/client/demo_client.idl +++ b/examples/demo/client/demo_client.idl @@ -142,7 +142,7 @@ service Chaos { } } -program ProgramToDo { +program DemoClient { constructors { /// Program constructor (called once at the very beginning of the program lifetime) Default(); diff --git a/examples/demo/client/src/demo_client.rs b/examples/demo/client/src/demo_client.rs index 348e51cab..67bed37c4 100644 --- a/examples/demo/client/src/demo_client.rs +++ b/examples/demo/client/src/demo_client.rs @@ -1,14 +1,13 @@ // Code generated by sails-client-gen. DO NOT EDIT. +#[cfg(feature = "with_mocks")] +#[cfg(not(target_arch = "wasm32"))] +extern crate std; #[allow(unused_imports)] use core::num::NonZeroU8; #[allow(unused_imports)] use core::num::NonZeroU32; #[allow(unused_imports)] use sails_rs::{client::*, collections::*, prelude::*}; - -#[cfg(feature = "with_mocks")] -#[cfg(not(target_arch = "wasm32"))] -extern crate std; pub struct DemoClientProgram; impl sails_rs::client::Program for DemoClientProgram {} pub trait DemoClient { diff --git a/examples/no-svcs-prog/wasm/no_svcs_prog.idl b/examples/no-svcs-prog/wasm/no_svcs_prog.idl index 443f67a40..429b45ddd 100644 --- a/examples/no-svcs-prog/wasm/no_svcs_prog.idl +++ b/examples/no-svcs-prog/wasm/no_svcs_prog.idl @@ -1,7 +1,7 @@ !@sails: 0.9.2 -program ProgramToDo { +program NoSvcsProg { constructors { Create(); } diff --git a/examples/proxy/src/main-idl-gen.rs b/examples/proxy/src/main-idl-gen.rs index 71e37f3e7..bb6a5e3a9 100644 --- a/examples/proxy/src/main-idl-gen.rs +++ b/examples/proxy/src/main-idl-gen.rs @@ -3,10 +3,9 @@ use std::path::PathBuf; fn main() { // TODO: Switch to new IDL generator crate - /* - sails_idl_gen::generate_idl_to_file::( - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("proxy.idl"), - ) - .unwrap(); - */ + // sails_rename::generate_idl_to_file::( + // Some("ProxyProgram"), + // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("proxy.idl"), + // ) + // .unwrap(); } diff --git a/examples/redirect/client/redirect_client.idl b/examples/redirect/client/redirect_client.idl index d1769e7d7..475104d88 100644 --- a/examples/redirect/client/redirect_client.idl +++ b/examples/redirect/client/redirect_client.idl @@ -11,7 +11,7 @@ service Redirect { } } -program ProgramToDo { +program RedirectClient { constructors { New(); } diff --git a/examples/redirect/proxy-client/redirect_proxy_client.idl b/examples/redirect/proxy-client/redirect_proxy_client.idl index 758e4307e..4b2634273 100644 --- a/examples/redirect/proxy-client/redirect_proxy_client.idl +++ b/examples/redirect/proxy-client/redirect_proxy_client.idl @@ -9,7 +9,7 @@ service Proxy { } } -program ProgramToDo { +program RedirectProxyClient { constructors { /// Proxy Program's constructor New(target: ActorId); diff --git a/examples/rmrk/catalog/wasm/rmrk-catalog.idl b/examples/rmrk/catalog/wasm/rmrk-catalog.idl index de4ca9807..1932654fd 100644 --- a/examples/rmrk/catalog/wasm/rmrk-catalog.idl +++ b/examples/rmrk/catalog/wasm/rmrk-catalog.idl @@ -49,7 +49,7 @@ service RmrkCatalog { } } -program ProgramToDo { +program RmrkCatalog { constructors { New(); } diff --git a/examples/rmrk/resource/app/src/rmrk_catalog.rs b/examples/rmrk/resource/app/src/rmrk_catalog.rs index d60605d4d..fc847ad85 100644 --- a/examples/rmrk/resource/app/src/rmrk_catalog.rs +++ b/examples/rmrk/resource/app/src/rmrk_catalog.rs @@ -1,10 +1,9 @@ // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "mockall")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { diff --git a/rs/client-gen/src/ctor_generators.rs b/rs/client-gen/src/ctor_generators.rs index a1fe19b10..9da4056c2 100644 --- a/rs/client-gen/src/ctor_generators.rs +++ b/rs/client-gen/src/ctor_generators.rs @@ -5,7 +5,7 @@ use rust::Tokens; use sails_idl_parser_v2::{ast, visitor::Visitor}; pub(crate) struct CtorGenerator<'ast> { - service_name: &'ast str, + program_name: &'ast str, sails_path: &'ast str, ctor_tokens: Tokens, io_tokens: Tokens, @@ -13,9 +13,9 @@ pub(crate) struct CtorGenerator<'ast> { } impl<'ast> CtorGenerator<'ast> { - pub(crate) fn new(service_name: &'ast str, sails_path: &'ast str) -> Self { + pub(crate) fn new(program_name: &'ast str, sails_path: &'ast str) -> Self { Self { - service_name, + program_name, sails_path, ctor_tokens: Tokens::new(), io_tokens: Tokens::new(), @@ -25,12 +25,12 @@ impl<'ast> CtorGenerator<'ast> { pub(crate) fn finalize(self) -> Tokens { quote! { - pub trait $(self.service_name)Ctors { + pub trait $(self.program_name)Ctors { type Env: $(self.sails_path)::client::GearEnv; $(self.trait_ctors_tokens) } - impl $(self.service_name)Ctors for $(self.sails_path)::client::Deployment<$(self.service_name)Program, E> { + impl $(self.program_name)Ctors for $(self.sails_path)::client::Deployment<$(self.program_name)Program, E> { type Env = E; $(self.ctor_tokens) } @@ -63,12 +63,12 @@ impl<'ast> Visitor<'ast> for CtorGenerator<'ast> { quote_in! { self.trait_ctors_tokens => $['\r'] - fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.service_name)Program, io::$fn_name, Self::Env>; + fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.program_name)Program, io::$fn_name, Self::Env>; }; quote_in! { self.ctor_tokens => $['\r'] - fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.service_name)Program, io::$fn_name, Self::Env> { + fn $fn_name_snake (self, $params_with_types) -> $(self.sails_path)::client::PendingCtor<$(self.program_name)Program, io::$fn_name, Self::Env> { self.pending_ctor($args) } }; diff --git a/rs/client-gen/src/lib.rs b/rs/client-gen/src/lib.rs index 2805c821c..a26c37ea6 100644 --- a/rs/client-gen/src/lib.rs +++ b/rs/client-gen/src/lib.rs @@ -1,8 +1,7 @@ use anyhow::{Context, Result}; -use convert_case::{Case, Casing}; use root_generator::RootGenerator; use sails_idl_parser_v2::{parse_idl, visitor}; -use std::{collections::HashMap, ffi::OsStr, fs, io::Write, path::Path}; +use std::{collections::HashMap, fs, io::Write, path::Path}; mod ctor_generators; mod events_generator; @@ -106,11 +105,8 @@ impl<'ast> ClientGenerator<'ast, IdlPath<'ast>> { let idl = fs::read_to_string(idl_path) .with_context(|| format!("Failed to open {} for reading", idl_path.display()))?; - let file_name = idl_path.file_stem().unwrap_or(OsStr::new("service")); - let service_name = file_name.to_string_lossy().to_case(Case::Pascal); - self.with_idl(&idl) - .generate_to(&service_name, client_path) + .generate_to(client_path) .context("failed to generate client")?; Ok(()) } @@ -121,11 +117,8 @@ impl<'ast> ClientGenerator<'ast, IdlPath<'ast>> { let idl = fs::read_to_string(idl_path) .with_context(|| format!("Failed to open {} for reading", idl_path.display()))?; - let file_name = idl_path.file_stem().unwrap_or(OsStr::new("service")); - let service_name = file_name.to_string_lossy().to_case(Case::Pascal); - self.with_idl(&idl) - .generate_to(&service_name, out_path) + .generate_to(out_path) .context("failed to generate client")?; Ok(()) } @@ -156,11 +149,10 @@ impl<'ast> ClientGenerator<'ast, IdlString<'ast>> { } } - pub fn generate(self, anonymous_service_name: &str) -> Result { + pub fn generate(self) -> Result { let idl = self.idl.0; let sails_path = self.sails_path.unwrap_or(SAILS); let mut generator = RootGenerator::new( - anonymous_service_name, self.mocks_feature_name, sails_path, self.external_types, @@ -177,15 +169,9 @@ impl<'ast> ClientGenerator<'ast, IdlString<'ast>> { Ok(code) } - pub fn generate_to( - self, - anonymous_service_name: &str, - out_path: impl AsRef, - ) -> Result<()> { + pub fn generate_to(self, out_path: impl AsRef) -> Result<()> { let out_path = out_path.as_ref(); - let code = self - .generate(anonymous_service_name) - .context("failed to generate client")?; + let code = self.generate().context("failed to generate client")?; fs::write(out_path, code).with_context(|| { format!("Failed to write generated client to {}", out_path.display()) diff --git a/rs/client-gen/src/root_generator.rs b/rs/client-gen/src/root_generator.rs index a05eb8b7b..d7987b7af 100644 --- a/rs/client-gen/src/root_generator.rs +++ b/rs/client-gen/src/root_generator.rs @@ -12,7 +12,7 @@ pub(crate) struct RootGenerator<'ast> { tokens: Tokens, service_impl_tokens: Tokens, service_trait_tokens: Tokens, - anonymous_service_name: &'ast str, + program_name: Option<&'ast str>, mocks_feature_name: Option<&'ast str>, sails_path: &'ast str, external_types: HashMap<&'ast str, &'ast str>, @@ -23,17 +23,16 @@ pub(crate) struct RootGenerator<'ast> { impl<'ast> RootGenerator<'ast> { pub(crate) fn new( - anonymous_service_name: &'ast str, mocks_feature_name: Option<&'ast str>, sails_path: &'ast str, external_types: HashMap<&'ast str, &'ast str>, no_derive_traits: bool, ) -> Self { Self { - anonymous_service_name, tokens: Tokens::new(), service_impl_tokens: Tokens::new(), service_trait_tokens: Tokens::new(), + program_name: None, mocks_feature_name, sails_path, external_types, @@ -44,7 +43,7 @@ impl<'ast> RootGenerator<'ast> { } pub(crate) fn finalize(self, with_no_std: bool) -> String { - let extern_std = if let Some(mocks_feature_name) = self.mocks_feature_name { + let mut tokens = if let Some(mocks_feature_name) = self.mocks_feature_name { quote! { $['\n'] #[cfg(feature = $(quoted(mocks_feature_name)))] @@ -55,7 +54,7 @@ impl<'ast> RootGenerator<'ast> { Tokens::new() }; - let mut tokens = quote! { + quote_in! { tokens => #[allow(unused_imports)] use $(self.sails_path)::{client::*, collections::*, prelude::*}; }; @@ -67,26 +66,25 @@ impl<'ast> RootGenerator<'ast> { }; } - let program_name = &self.anonymous_service_name.to_case(Case::Pascal); - quote_in! { tokens => - $extern_std + if let Some(program_name) = self.program_name { + quote_in! { tokens => + pub struct $(program_name)Program; - pub struct $(program_name)Program; + impl $(self.sails_path)::client::Program for $(program_name)Program {} - impl $(self.sails_path)::client::Program for $(program_name)Program {} + pub trait $program_name { + type Env: $(self.sails_path)::client::GearEnv; + $(self.service_trait_tokens) + } - pub trait $program_name { - type Env: $(self.sails_path)::client::GearEnv; - $(self.service_trait_tokens) - } - - impl $program_name for $(self.sails_path)::client::Actor<$(program_name)Program, E> { - type Env = E; - $(self.service_impl_tokens) - } + impl $program_name for $(self.sails_path)::client::Actor<$(program_name)Program, E> { + type Env = E; + $(self.service_impl_tokens) + } + }; + } - $(self.tokens) - }; + tokens.extend(self.tokens); let mut result = tokens.to_file_string().unwrap(); @@ -105,7 +103,9 @@ impl<'ast> RootGenerator<'ast> { impl<'ast> Visitor<'ast> for RootGenerator<'ast> { fn visit_program_unit(&mut self, program: &'ast ast::ProgramUnit) { - let mut ctor_gen = CtorGenerator::new(self.anonymous_service_name, self.sails_path); + self.program_name = Some(&program.name); + + let mut ctor_gen = CtorGenerator::new(&program.name, self.sails_path); ctor_gen.visit_program_unit(program); self.tokens.extend(ctor_gen.finalize()); @@ -118,14 +118,8 @@ impl<'ast> Visitor<'ast> for RootGenerator<'ast> { } fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) { - let service_name = if service.name.is_empty() { - self.anonymous_service_name - } else { - &service.name - }; - let mut client_gen = ServiceGenerator::new( - service_name, + &service.name, self.sails_path, &self.external_types, self.mocks_feature_name, diff --git a/rs/client-gen/tests/generator.rs b/rs/client-gen/tests/generator.rs index 2831353ef..8e1ddedba 100644 --- a/rs/client-gen/tests/generator.rs +++ b/rs/client-gen/tests/generator.rs @@ -4,42 +4,42 @@ use sails_client_gen::ClientGenerator; fn test_basic_works() { let idl = include_str!("idls/basic_works.idl"); - insta::assert_snapshot!(gen_client(idl, "Basic")); + insta::assert_snapshot!(gen_client(idl)); } #[test] fn test_complex_type_generation_works() { const IDL: &str = include_str!("idls/complex_type_generation_works.idl"); - insta::assert_snapshot!(gen_client(IDL, "ComplexTypesProgram")); + insta::assert_snapshot!(gen_client(IDL)); } #[test] fn test_scope_resolution() { const IDL: &str = include_str!("idls/scope_test.idl"); - insta::assert_snapshot!(gen_client(IDL, "MyProgram")); + insta::assert_snapshot!(gen_client(IDL)); } #[test] fn test_multiple_services() { let idl = include_str!("idls/multiple_services.idl"); - insta::assert_snapshot!(gen_client(idl, "Multiple")); + insta::assert_snapshot!(gen_client(idl)); } #[test] fn test_rmrk_works() { const IDL: &str = include_str!("idls/rmrk_works.idl"); - insta::assert_snapshot!(gen_client(IDL, "RmrkCatalog")); + insta::assert_snapshot!(gen_client(IDL)); } #[test] fn test_events_works() { let idl = include_str!("idls/events_works.idl"); - insta::assert_snapshot!(gen_client(idl, "ServiceWithEvents")); + insta::assert_snapshot!(gen_client(idl)); } #[test] @@ -48,7 +48,7 @@ fn full_with_sails_path() { let code = ClientGenerator::from_idl(IDL) .with_sails_crate("my_crate::sails") - .generate("FullCoverageProgram") // Use new program name + .generate() // Use new program name .expect("generate client"); insta::assert_snapshot!(code); } @@ -61,14 +61,14 @@ fn test_external_types() { .with_sails_crate("my_crate::sails") .with_external_type("MyParam", "my_crate::MyParam") .with_no_derive_traits() - .generate("Service") + .generate() .expect("generate client"); insta::assert_snapshot!(code); } -fn gen_client(program: &str, service_name: &str) -> String { +fn gen_client(program: &str) -> String { ClientGenerator::from_idl(program) .with_mocks("with_mocks") - .generate(service_name) + .generate() .expect("generate client") } diff --git a/rs/client-gen/tests/snapshots/generator__basic_works.snap b/rs/client-gen/tests/snapshots/generator__basic_works.snap index 9e41253b0..e6d174636 100644 --- a/rs/client-gen/tests/snapshots/generator__basic_works.snap +++ b/rs/client-gen/tests/snapshots/generator__basic_works.snap @@ -1,22 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(idl, \"Basic\")" +expression: gen_client(idl) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; -pub struct BasicProgram; -impl sails_rs::client::Program for BasicProgram {} -pub trait Basic { - type Env: sails_rs::client::GearEnv; -} -impl Basic for sails_rs::client::Actor { - type Env = E; -} +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub mod basic { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap b/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap index 283896a87..6e33ef0bf 100644 --- a/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap +++ b/rs/client-gen/tests/snapshots/generator__complex_type_generation_works.snap @@ -1,14 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(IDL, \"ComplexTypesProgram\")" +expression: gen_client(IDL) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct ComplexTypesProgramProgram; impl sails_rs::client::Program for ComplexTypesProgramProgram {} pub trait ComplexTypesProgram { diff --git a/rs/client-gen/tests/snapshots/generator__events_works.snap b/rs/client-gen/tests/snapshots/generator__events_works.snap index dbdb62077..713d84a00 100644 --- a/rs/client-gen/tests/snapshots/generator__events_works.snap +++ b/rs/client-gen/tests/snapshots/generator__events_works.snap @@ -1,24 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(idl, \"ServiceWithEvents\")" +expression: gen_client(idl) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; -pub struct ServiceWithEventsProgram; -impl sails_rs::client::Program for ServiceWithEventsProgram {} -pub trait ServiceWithEvents { - type Env: sails_rs::client::GearEnv; -} -impl ServiceWithEvents - for sails_rs::client::Actor -{ - type Env = E; -} +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub mod service_with_events { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__external_types.snap b/rs/client-gen/tests/snapshots/generator__external_types.snap index 8f3fe6d09..11fd38016 100644 --- a/rs/client-gen/tests/snapshots/generator__external_types.snap +++ b/rs/client-gen/tests/snapshots/generator__external_types.snap @@ -7,16 +7,6 @@ expression: code use my_crate::MyParam; #[allow(unused_imports)] use my_crate::sails::{client::*, collections::*, prelude::*}; -pub struct ServiceProgram; -impl my_crate::sails::client::Program for ServiceProgram {} -pub trait Service { - type Env: my_crate::sails::client::GearEnv; -} -impl Service - for my_crate::sails::client::Actor -{ - type Env = E; -} pub mod service { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__multiple_services.snap b/rs/client-gen/tests/snapshots/generator__multiple_services.snap index bb804da0a..6318f2b8e 100644 --- a/rs/client-gen/tests/snapshots/generator__multiple_services.snap +++ b/rs/client-gen/tests/snapshots/generator__multiple_services.snap @@ -1,22 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(idl, \"Multiple\")" +expression: gen_client(idl) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; -pub struct MultipleProgram; -impl sails_rs::client::Program for MultipleProgram {} -pub trait Multiple { - type Env: sails_rs::client::GearEnv; -} -impl Multiple for sails_rs::client::Actor { - type Env = E; -} +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub mod multiple { use super::*; diff --git a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap index e19dfe451..71639eda9 100644 --- a/rs/client-gen/tests/snapshots/generator__rmrk_works.snap +++ b/rs/client-gen/tests/snapshots/generator__rmrk_works.snap @@ -1,14 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(IDL, \"RmrkCatalog\")" +expression: gen_client(IDL) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct RmrkCatalogProgram; impl sails_rs::client::Program for RmrkCatalogProgram {} pub trait RmrkCatalog { diff --git a/rs/client-gen/tests/snapshots/generator__scope_resolution.snap b/rs/client-gen/tests/snapshots/generator__scope_resolution.snap index 52b38c88c..a27d0c6ba 100644 --- a/rs/client-gen/tests/snapshots/generator__scope_resolution.snap +++ b/rs/client-gen/tests/snapshots/generator__scope_resolution.snap @@ -1,14 +1,13 @@ --- source: rs/client-gen/tests/generator.rs -expression: "gen_client(IDL, \"MyProgram\")" +expression: gen_client(IDL) --- // Code generated by sails-client-gen. DO NOT EDIT. -#[allow(unused_imports)] -use sails_rs::{client::*, collections::*, prelude::*}; - #[cfg(feature = "with_mocks")] #[cfg(not(target_arch = "wasm32"))] extern crate std; +#[allow(unused_imports)] +use sails_rs::{client::*, collections::*, prelude::*}; pub struct MyProgramProgram; impl sails_rs::client::Program for MyProgramProgram {} pub trait MyProgram { diff --git a/rs/ethexe/ethapp_with_events/tests/insta.rs b/rs/ethexe/ethapp_with_events/tests/insta.rs index 6aa8a27a8..9e011f66c 100644 --- a/rs/ethexe/ethapp_with_events/tests/insta.rs +++ b/rs/ethexe/ethapp_with_events/tests/insta.rs @@ -1,7 +1,7 @@ #[test] fn ethapp_with_events_generate_idl() { let mut idl = Vec::new(); - sails_rs::generate_idl::(&mut idl).unwrap(); + sails_rs::generate_idl::(Some("MyProgram"), &mut idl).unwrap(); let idl = String::from_utf8(idl).unwrap(); insta::assert_snapshot!(idl); } diff --git a/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap b/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap index 354975fb5..7c02a8bd6 100644 --- a/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap +++ b/rs/ethexe/ethapp_with_events/tests/snapshots/insta__ethapp_with_events_generate_idl.snap @@ -26,7 +26,7 @@ service Svc2 { } } -program ProgramToDo { +program MyProgram { constructors { Create(); } diff --git a/rs/idl-gen/src/meta.rs b/rs/idl-gen/src/meta.rs deleted file mode 100644 index 2dba692ac..000000000 --- a/rs/idl-gen/src/meta.rs +++ /dev/null @@ -1,372 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2021-2023 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Struct describing the types of a service comprised of command and query handlers. - -use crate::{ - errors::{Error, Result}, - type_names, -}; -use gprimitives::*; -use sails_idl_meta::*; -use scale_info::{ - Field, MetaType, PortableRegistry, PortableType, Registry, TypeDef, Variant, form::PortableForm, -}; - -struct CtorFuncMeta(String, u32, Vec>, Vec); - -struct ServiceFuncMeta(String, u32, Vec>, u32, Vec); - -pub(crate) struct ExpandedProgramMeta { - registry: PortableRegistry, - builtin_type_ids: Vec, - ctors_type_id: Option, - ctors: Vec, - services: Vec, -} - -impl ExpandedProgramMeta { - pub fn new( - ctors: Option, - services: impl Iterator, - ) -> Result { - let mut registry = Registry::new(); - let builtin_type_ids = registry - .register_types([ - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - MetaType::new::(), - ]) - .iter() - .map(|t| t.id) - .collect::>(); - let ctors_type_id = ctors.map(|ctors| registry.register_type(&ctors).id); - let services_data = services - .map(|(sname, sm)| { - ( - sname, - Self::flat_meta(&sm, |sm| sm.commands()) - .into_iter() - .map(|mt| registry.register_type(mt).id) - .collect::>(), - Self::flat_meta(&sm, |sm| sm.queries()) - .into_iter() - .map(|mt| registry.register_type(mt).id) - .collect::>(), - Self::flat_meta(&sm, |sm| sm.events()) - .into_iter() - .map(|mt| registry.register_type(mt).id) - .collect::>(), - ) - }) - .collect::>(); - let registry = PortableRegistry::from(registry); - let ctors = Self::ctor_funcs(®istry, ctors_type_id)?; - let services = services_data - .into_iter() - .map(|(sname, ct_ids, qt_ids, et_ids)| { - ExpandedServiceMeta::new(®istry, sname, ct_ids, qt_ids, et_ids) - }) - .collect::>>()?; - Ok(Self { - registry, - builtin_type_ids, - ctors_type_id, - ctors, - services, - }) - } - - /// Returns complex types introduced by program only - pub fn types(&self) -> impl Iterator { - self.registry.types.iter().filter(|ty| { - !ty.ty.path.namespace().is_empty() - && self.ctors_type_id.is_none_or(|id| id != ty.id) - && !self.commands_type_ids().any(|id| id == ty.id) - && !self.queries_type_ids().any(|id| id == ty.id) - && !self.events_type_ids().any(|id| id == ty.id) - && !self.ctor_params_type_ids().any(|id| id == ty.id) - && !self.command_params_type_ids().any(|id| id == ty.id) - && !self.query_params_type_ids().any(|id| id == ty.id) - && !self.builtin_type_ids.contains(&ty.id) - }) - } - - pub fn ctors(&self) -> impl Iterator>, &Vec)> { - self.ctors.iter().map(|c| (c.0.as_str(), &c.2, &c.3)) - } - - pub fn services(&self) -> impl Iterator { - self.services.iter() - } - - /// Returns names for all types used by program including primitive, complex and "internal" ones. - /// Each type name index corresponds to id of the type - pub fn type_names(&self) -> Result> { - let names = type_names::resolve(self.registry.types.iter())?; - Ok(names.into_iter().map(|i| i.1)) - } - - fn ctor_funcs( - registry: &PortableRegistry, - func_type_id: Option, - ) -> Result> { - if func_type_id.is_none() { - return Ok(Vec::new()); - } - let func_type_id = func_type_id.unwrap(); - any_funcs(registry, func_type_id)? - .map(|c| { - if c.fields.len() != 1 { - Err(Error::FuncMetaIsInvalid(format!( - "ctor `{}` has invalid number of fields", - c.name - ))) - } else { - let params_type = registry.resolve(c.fields[0].ty.id).unwrap_or_else(|| { - panic!( - "ctor params type id {} not found while it was registered previously", - c.fields[0].ty.id - ) - }); - if let TypeDef::Composite(params_type) = ¶ms_type.type_def { - Ok(CtorFuncMeta( - c.name.to_string(), - c.fields[0].ty.id, - params_type.fields.to_vec(), - c.docs.iter().map(|s| s.to_string()).collect(), - )) - } else { - Err(Error::FuncMetaIsInvalid(format!( - "ctor `{}` params type is not a composite", - c.name - ))) - } - } - }) - .collect() - } - - fn flat_meta( - service_meta: &AnyServiceMeta, - meta: fn(&AnyServiceMeta) -> &MetaType, - ) -> Vec<&MetaType> { - let mut metas = vec![meta(service_meta)]; - for (_, base_service_meta) in service_meta.base_services() { - metas.extend(Self::flat_meta(base_service_meta, meta)); - } - metas - } - - fn commands_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.commands_type_ids.iter().copied()) - } - - fn queries_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.queries_type_ids.iter().copied()) - } - - fn events_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.events_type_ids.iter().copied()) - } - - fn ctor_params_type_ids(&self) -> impl Iterator + '_ { - self.ctors.iter().map(|v| v.1) - } - - fn command_params_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.commands.iter().chain(&s.overriden_commands).map(|v| v.1)) - } - - fn query_params_type_ids(&self) -> impl Iterator + '_ { - self.services - .iter() - .flat_map(|s| s.queries.iter().chain(&s.overriden_queries).map(|v| v.1)) - } -} - -pub(crate) struct ExpandedServiceMeta { - name: &'static str, - commands_type_ids: Vec, - commands: Vec, - overriden_commands: Vec, - queries_type_ids: Vec, - queries: Vec, - overriden_queries: Vec, - events_type_ids: Vec, - events: Vec>, -} - -impl ExpandedServiceMeta { - fn new( - registry: &PortableRegistry, - name: &'static str, - commands_type_ids: Vec, - queries_type_ids: Vec, - events_type_ids: Vec, - ) -> Result { - let (commands, overriden_commands) = - Self::service_funcs(registry, commands_type_ids.iter().copied())?; - let (queries, overriden_queries) = - Self::service_funcs(registry, queries_type_ids.iter().copied())?; - let events = Self::event_variants(registry, events_type_ids.iter().copied())?; - Ok(Self { - name, - commands_type_ids, - commands, - overriden_commands, - queries_type_ids, - queries, - overriden_queries, - events_type_ids, - events, - }) - } - - pub fn name(&self) -> &str { - self.name - } - - pub fn commands( - &self, - ) -> impl Iterator>, u32, &Vec)> { - self.commands - .iter() - .map(|c| (c.0.as_str(), &c.2, c.3, &c.4)) - } - - pub fn queries( - &self, - ) -> impl Iterator>, u32, &Vec)> { - self.queries.iter().map(|c| (c.0.as_str(), &c.2, c.3, &c.4)) - } - - pub fn events(&self) -> impl Iterator> { - self.events.iter() - } - - fn service_funcs( - registry: &PortableRegistry, - func_type_ids: impl Iterator, - ) -> Result<(Vec, Vec)> { - let mut funcs_meta = Vec::new(); - let mut overriden_funcs_meta = Vec::new(); - for func_type_id in func_type_ids { - for func_descr in any_funcs(registry, func_type_id)? { - if func_descr.fields.len() != 2 { - return Err(Error::FuncMetaIsInvalid(format!( - "func `{}` has invalid number of fields", - func_descr.name - ))); - } - let func_params_type = registry.resolve(func_descr.fields[0].ty.id).unwrap_or_else( - || { - panic!( - "func params type id {} not found while it was registered previously", - func_descr.fields[0].ty.id - ) - }, - ); - if let TypeDef::Composite(func_params_type) = &func_params_type.type_def { - let func_meta = ServiceFuncMeta( - func_descr.name.to_string(), - func_descr.fields[0].ty.id, - func_params_type.fields.to_vec(), - func_descr.fields[1].ty.id, - func_descr.docs.iter().map(|s| s.to_string()).collect(), - ); - if !funcs_meta - .iter() - .any(|fm: &ServiceFuncMeta| fm.0 == func_meta.0) - { - funcs_meta.push(func_meta); - } else { - overriden_funcs_meta.push(func_meta); - } - } else { - return Err(Error::FuncMetaIsInvalid(format!( - "func `{}` params type is not a composite", - func_descr.name - ))); - } - } - } - Ok((funcs_meta, overriden_funcs_meta)) - } - - fn event_variants( - registry: &PortableRegistry, - events_type_ids: impl Iterator, - ) -> Result>> { - let mut events_variants = Vec::new(); - for events_type_id in events_type_ids { - let events = registry.resolve(events_type_id).unwrap_or_else(|| { - panic!( - "events type id {events_type_id} not found while it was registered previously" - ) - }); - if let TypeDef::Variant(variant) = &events.type_def { - for event_variant in &variant.variants { - if events_variants - .iter() - .any(|ev: &Variant| ev.name == event_variant.name) - { - return Err(Error::EventMetaIsAmbiguous(format!( - "events type id {} contains ambiguous event variant `{}`", - events_type_id, event_variant.name - ))); - } - events_variants.push(event_variant.clone()); - } - } else { - return Err(Error::EventMetaIsInvalid(format!( - "events type id {events_type_id} references a type that is not a variant" - ))); - } - } - Ok(events_variants) - } -} - -fn any_funcs( - registry: &PortableRegistry, - func_type_id: u32, -) -> Result>> { - let funcs = registry.resolve(func_type_id).unwrap_or_else(|| { - panic!("func type id {func_type_id} not found while it was registered previously") - }); - if let TypeDef::Variant(variant) = &funcs.type_def { - Ok(variant.variants.iter()) - } else { - Err(Error::FuncMetaIsInvalid(format!( - "func type id {func_type_id} references a type that is not a variant" - ))) - } -} diff --git a/rs/idl-gen/src/type_names.rs b/rs/idl-gen/src/type_names.rs deleted file mode 100644 index 1c57a24b4..000000000 --- a/rs/idl-gen/src/type_names.rs +++ /dev/null @@ -1,1074 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2021-2023 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Type names resolution. - -use crate::errors::{Error, Result}; -use convert_case::{Case, Casing}; -use core::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128}; -use gprimitives::*; -use scale_info::{ - PortableType, Type, TypeDef, TypeDefArray, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, - TypeInfo, form::PortableForm, -}; -use std::{ - collections::{BTreeMap, HashMap}, - rc::Rc, - result::Result as StdResult, - sync::OnceLock, -}; - -pub(super) fn resolve<'a>( - types: impl Iterator, -) -> Result> { - let types = types - .map(|t| (t.id, t)) - .collect::>(); - let type_names = types.iter().try_fold( - ( - BTreeMap::::new(), - HashMap::<(String, Vec), u32>::new(), - ), - |mut type_names, ty| { - resolve_type_name(&types, *ty.0, &mut type_names.0, &mut type_names.1) - .map(|_| type_names) - }, - ); - type_names.map(|type_names| { - type_names - .0 - .iter() - .map(|(id, name)| (*id, name.as_string(false, &type_names.1))) - .collect() - }) -} - -fn resolve_type_name( - types: &BTreeMap, - type_id: u32, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, -) -> Result { - if let Some(type_name) = resolved_type_names.get(&type_id) { - return Ok(type_name.clone()); - } - - let type_info = types - .get(&type_id) - .map(|t| &t.ty) - .ok_or_else(|| Error::TypeIdIsUnknown(type_id))?; - - let type_name: RcTypeName = match &type_info.type_def { - TypeDef::Tuple(tuple_def) => Rc::new(TupleTypeName::new( - types, - tuple_def, - resolved_type_names, - by_path_type_names, - )?), - TypeDef::Sequence(vector_def) => Rc::new(VectorTypeName::new( - types, - vector_def, - resolved_type_names, - by_path_type_names, - )?), - TypeDef::Array(array_def) => Rc::new(ArrayTypeName::new( - types, - array_def, - resolved_type_names, - by_path_type_names, - )?), - TypeDef::Composite(_) => { - if BTreeMapTypeName::is_btree_map_type(type_info) { - Rc::new(BTreeMapTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } else if actor_id::TypeNameImpl::is_type(type_info) { - Rc::new(actor_id::TypeNameImpl::new()) - } else if message_id::TypeNameImpl::is_type(type_info) { - Rc::new(message_id::TypeNameImpl::new()) - } else if code_id::TypeNameImpl::is_type(type_info) { - Rc::new(code_id::TypeNameImpl::new()) - } else if h160::TypeNameImpl::is_type(type_info) { - Rc::new(h160::TypeNameImpl::new()) - } else if h256::TypeNameImpl::is_type(type_info) { - Rc::new(h256::TypeNameImpl::new()) - } else if u256::TypeNameImpl::is_type(type_info) { - Rc::new(u256::TypeNameImpl::new()) - } else if nat8::TypeNameImpl::is_type(type_info) { - Rc::new(nat8::TypeNameImpl::new()) - } else if nat16::TypeNameImpl::is_type(type_info) { - Rc::new(nat16::TypeNameImpl::new()) - } else if nat32::TypeNameImpl::is_type(type_info) { - Rc::new(nat32::TypeNameImpl::new()) - } else if nat64::TypeNameImpl::is_type(type_info) { - Rc::new(nat64::TypeNameImpl::new()) - } else if nat128::TypeNameImpl::is_type(type_info) { - Rc::new(nat128::TypeNameImpl::new()) - } else if nat256::TypeNameImpl::is_type(type_info) { - Rc::new(nat256::TypeNameImpl::new()) - } else { - Rc::new(ByPathTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } - } - TypeDef::Variant(_) => { - if ResultTypeName::is_result_type(type_info) { - Rc::new(ResultTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } else if OptionTypeName::is_option_type(type_info) { - Rc::new(OptionTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } else { - Rc::new(ByPathTypeName::new( - types, - type_info, - resolved_type_names, - by_path_type_names, - )?) - } - } - TypeDef::Primitive(primitive_def) => Rc::new(PrimitiveTypeName::new(primitive_def)?), - _ => { - return Err(Error::TypeIsUnsupported(format!("{type_info:?}"))); - } - }; - - resolved_type_names.insert(type_id, type_name.clone()); - Ok(type_name) -} - -type RcTypeName = Rc; - -trait TypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String; // Make returning &str + use OnceCell to cache the result -} - -/// By path type name resolution. -struct ByPathTypeName { - possible_names: Vec<(String, Vec)>, - type_param_type_names: Vec, -} - -impl ByPathTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let type_params = type_info.type_params.iter().try_fold( - ( - Vec::with_capacity(type_info.type_params.len()), - Vec::with_capacity(type_info.type_params.len()), - ), - |(mut type_param_ids, mut type_param_type_names), type_param| { - let type_param_id = type_param - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .id; - let type_param_type_name = resolve_type_name( - types, - type_param_id, - resolved_type_names, - by_path_type_names, - )?; - type_param_ids.push(type_param_id); - type_param_type_names.push(type_param_type_name); - Ok::<(Vec, Vec>), Error>(( - type_param_ids, - type_param_type_names, - )) - }, - )?; - - let mut possible_names = Self::possible_names_by_path(type_info).fold( - Vec::with_capacity(type_info.path.segments.len() + 1), - |mut possible_names, name| { - possible_names.push((name.clone(), type_params.0.clone())); - let name_ref_count = by_path_type_names - .entry((name.clone(), type_params.0.clone())) - .or_default(); - *name_ref_count += 1; - possible_names - }, - ); - if let Some(first_name) = possible_names.first() { - // add numbered type name like `TypeName1`, `TypeName2` as last name - // to solve name conflict with const generic parameters `` - let name_ref_count = by_path_type_names.get(first_name).unwrap_or(&0); - let name = format!("{}{}", first_name.0, name_ref_count); - possible_names.push((name.clone(), first_name.1.clone())); - let name_ref_count = by_path_type_names - .entry((name.clone(), type_params.0.clone())) - .or_default(); - *name_ref_count += 1; - } else { - return Err(Error::TypeIsUnsupported(format!("{type_info:?}"))); - } - - Ok(Self { - possible_names, - type_param_type_names: type_params.1, - }) - } - - fn possible_names_by_path(type_info: &Type) -> impl Iterator + '_ { - let mut name = String::default(); - type_info.path.segments.iter().rev().map(move |segment| { - name = segment.to_case(Case::Pascal) + &name; - name.clone() - }) - } -} - -impl TypeName for ByPathTypeName { - fn as_string( - &self, - _for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let name = self - .possible_names - .iter() - .find(|possible_name| { - by_path_type_names - .get(possible_name) - .is_some_and(|ref_count| *ref_count == 1) - }) - .unwrap_or_else(|| self.possible_names.last().unwrap()); - if self.type_param_type_names.is_empty() { - name.0.clone() - } else { - let type_param_names = self - .type_param_type_names - .iter() - .map(|tn| tn.as_string(true, by_path_type_names)) - .collect::>() - .join("And"); - format!("{}For{}", name.0, type_param_names) - } - } -} - -/// BTreeMap type name resolution. -struct BTreeMapTypeName { - key_type_name: RcTypeName, - value_type_name: RcTypeName, -} - -impl BTreeMapTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let key_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "K") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let value_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "V") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let key_type_name = resolve_type_name( - types, - key_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - let value_type_name = resolve_type_name( - types, - value_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { - key_type_name, - value_type_name, - }) - } - - pub fn is_btree_map_type(type_info: &Type) -> bool { - static BTREE_MAP_TYPE_INFO: OnceLock = OnceLock::new(); - let btree_map_type_info = BTREE_MAP_TYPE_INFO.get_or_init(BTreeMap::::type_info); - btree_map_type_info.path.segments == type_info.path.segments - } -} - -impl TypeName for BTreeMapTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let key_type_name = self - .key_type_name - .as_string(for_generic_param, by_path_type_names); - let value_type_name = self - .value_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("MapOf{key_type_name}To{value_type_name}") - } else { - format!("map ({key_type_name}, {value_type_name})") - } - } -} - -/// Result type name resolution. -struct ResultTypeName { - ok_type_name: RcTypeName, - err_type_name: RcTypeName, -} - -impl ResultTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let ok_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "T") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let err_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "E") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let ok_type_name = resolve_type_name( - types, - ok_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - let err_type_name = resolve_type_name( - types, - err_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { - ok_type_name, - err_type_name, - }) - } - - pub fn is_result_type(type_info: &Type) -> bool { - static RESULT_TYPE_INFO: OnceLock = OnceLock::new(); - let result_type_info = RESULT_TYPE_INFO.get_or_init(StdResult::<(), ()>::type_info); - result_type_info.path.segments == type_info.path.segments - } -} - -impl TypeName for ResultTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let ok_type_name = self - .ok_type_name - .as_string(for_generic_param, by_path_type_names); - let err_type_name = self - .err_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("ResultOf{ok_type_name}Or{err_type_name}") - } else { - format!("result ({ok_type_name}, {err_type_name})") - } - } -} - -/// Option type name resolution. -struct OptionTypeName { - some_type_name: RcTypeName, -} - -impl OptionTypeName { - pub fn new( - types: &BTreeMap, - type_info: &Type, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let some_type_id = type_info - .type_params - .iter() - .find(|param| param.name == "T") - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))? - .ty - .ok_or_else(|| Error::TypeIsUnsupported(format!("{type_info:?}")))?; - let some_type_name = resolve_type_name( - types, - some_type_id.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { some_type_name }) - } - - pub fn is_option_type(type_info: &Type) -> bool { - static OPTION_TYPE_INFO: OnceLock = OnceLock::new(); - let option_type_info = OPTION_TYPE_INFO.get_or_init(Option::<()>::type_info); - option_type_info.path.segments == type_info.path.segments - } -} - -impl TypeName for OptionTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let some_type_name = self - .some_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("OptOf{some_type_name}") - } else { - format!("opt {some_type_name}") - } - } -} - -/// Tuple type name resolution. -struct TupleTypeName { - field_type_names: Vec, -} - -impl TupleTypeName { - pub fn new( - types: &BTreeMap, - tuple_def: &TypeDefTuple, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let field_type_names = tuple_def - .fields - .iter() - .map(|field| { - resolve_type_name(types, field.id, resolved_type_names, by_path_type_names) - }) - .collect::>>()?; - Ok(Self { field_type_names }) - } -} - -impl TypeName for TupleTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - if self.field_type_names.is_empty() { - "null".into() - } else if for_generic_param { - format!( - "StructOf{}", - self.field_type_names - .iter() - .map(|tn| tn.as_string(for_generic_param, by_path_type_names)) - .collect::>() - .join("And") - ) - } else { - format!( - "struct {{ {} }}", - self.field_type_names - .iter() - .map(|tn| tn.as_string(for_generic_param, by_path_type_names)) - .collect::>() - .join(", ") - ) - } - } -} - -/// Vector type name resolution. -struct VectorTypeName { - item_type_name: RcTypeName, -} - -impl VectorTypeName { - pub fn new( - types: &BTreeMap, - vector_def: &TypeDefSequence, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let item_type_name = resolve_type_name( - types, - vector_def.type_param.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { item_type_name }) - } -} - -impl TypeName for VectorTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let item_type_name = self - .item_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("VecOf{item_type_name}") - } else { - format!("vec {item_type_name}") - } - } -} - -/// Array type name resolution. -struct ArrayTypeName { - item_type_name: RcTypeName, - len: u32, -} - -impl ArrayTypeName { - pub fn new( - types: &BTreeMap, - array_def: &TypeDefArray, - resolved_type_names: &mut BTreeMap, - by_path_type_names: &mut HashMap<(String, Vec), u32>, - ) -> Result { - let item_type_name = resolve_type_name( - types, - array_def.type_param.id, - resolved_type_names, - by_path_type_names, - )?; - Ok(Self { - item_type_name, - len: array_def.len, - }) - } -} - -impl TypeName for ArrayTypeName { - fn as_string( - &self, - for_generic_param: bool, - by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - let item_type_name = self - .item_type_name - .as_string(for_generic_param, by_path_type_names); - if for_generic_param { - format!("ArrOf{}{}", self.len, item_type_name) - } else { - format!("[{}, {}]", item_type_name, self.len) - } - } -} - -/// Primitive type name resolution. -struct PrimitiveTypeName { - name: &'static str, -} - -impl PrimitiveTypeName { - pub fn new(type_def: &TypeDefPrimitive) -> Result { - let name = match type_def { - TypeDefPrimitive::Bool => Ok("bool"), - TypeDefPrimitive::Char => Ok("char"), - TypeDefPrimitive::Str => Ok("str"), - TypeDefPrimitive::U8 => Ok("u8"), - TypeDefPrimitive::U16 => Ok("u16"), - TypeDefPrimitive::U32 => Ok("u32"), - TypeDefPrimitive::U64 => Ok("u64"), - TypeDefPrimitive::U128 => Ok("u128"), - TypeDefPrimitive::U256 => Err(Error::TypeIsUnsupported("u256".into())), // Rust doesn't have it - TypeDefPrimitive::I8 => Ok("i8"), - TypeDefPrimitive::I16 => Ok("i16"), - TypeDefPrimitive::I32 => Ok("i32"), - TypeDefPrimitive::I64 => Ok("i64"), - TypeDefPrimitive::I128 => Ok("i128"), - TypeDefPrimitive::I256 => Err(Error::TypeIsUnsupported("i256".into())), // Rust doesn't have it - }?; - Ok(Self { name }) - } -} - -impl TypeName for PrimitiveTypeName { - fn as_string( - &self, - for_generic_param: bool, - _by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - if for_generic_param { - self.name.to_case(Case::Pascal) - } else { - self.name.to_string() - } - } -} - -macro_rules! impl_primitive_alias_type_name { - ($primitive:ident, $alias:ident) => { - mod $alias { - use super::*; - - pub(super) struct TypeNameImpl; - - impl TypeNameImpl { - pub fn new() -> Self { - Self - } - - pub fn is_type(type_info: &Type) -> bool { - static TYPE_INFO: OnceLock = OnceLock::new(); - let info = TYPE_INFO.get_or_init($primitive::type_info); - info.path.segments == type_info.path.segments - } - } - - impl TypeName for TypeNameImpl { - fn as_string( - &self, - for_generic_param: bool, - _by_path_type_names: &HashMap<(String, Vec), u32>, - ) -> String { - if for_generic_param { - stringify!($primitive).into() - } else { - stringify!($alias).into() - } - } - } - } - }; -} - -impl_primitive_alias_type_name!(ActorId, actor_id); -impl_primitive_alias_type_name!(MessageId, message_id); -impl_primitive_alias_type_name!(CodeId, code_id); -impl_primitive_alias_type_name!(H160, h160); -impl_primitive_alias_type_name!(H256, h256); -impl_primitive_alias_type_name!(U256, u256); -impl_primitive_alias_type_name!(NonZeroU8, nat8); -impl_primitive_alias_type_name!(NonZeroU16, nat16); -impl_primitive_alias_type_name!(NonZeroU32, nat32); -impl_primitive_alias_type_name!(NonZeroU64, nat64); -impl_primitive_alias_type_name!(NonZeroU128, nat128); -impl_primitive_alias_type_name!(NonZeroU256, nat256); - -#[cfg(test)] -mod tests { - use std::result; - - use super::*; - use scale_info::{MetaType, PortableRegistry, Registry}; - - #[allow(dead_code)] - #[derive(TypeInfo)] - struct GenericStruct { - field: T, - } - - #[allow(dead_code)] - #[derive(TypeInfo)] - struct GenericConstStruct { - field: [T; N], - field2: [T; M], - } - - #[allow(dead_code)] - #[derive(TypeInfo)] - enum GenericEnum { - Variant1(T1), - Variant2(T2), - } - - #[allow(dead_code)] - mod mod_1 { - use super::*; - - #[derive(TypeInfo)] - pub struct T1 {} - - pub mod mod_2 { - use super::*; - - #[derive(TypeInfo)] - pub struct T2 {} - } - } - - #[allow(dead_code)] - mod mod_2 { - use super::*; - - #[derive(TypeInfo)] - pub struct T1 {} - - #[derive(TypeInfo)] - pub struct T2 {} - } - - #[test] - fn h256_u256_type_name_resolution_works() { - let mut registry = Registry::new(); - let h256_id = registry.register_type(&MetaType::new::()).id; - let h256_as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let u256_id = registry.register_type(&MetaType::new::()).id; - let u256_as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let h256_name = type_names.get(&h256_id).unwrap(); - assert_eq!(h256_name, "h256"); - let as_generic_param_name = type_names.get(&h256_as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForH256"); - let u256_name = type_names.get(&u256_id).unwrap(); - assert_eq!(u256_name, "u256"); - let as_generic_param_name = type_names.get(&u256_as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForU256"); - } - - #[test] - fn generic_struct_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_struct_id = registry - .register_type(&MetaType::new::>()) - .id; - let string_struct_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_struct_name = type_names.get(&u32_struct_id).unwrap(); - assert_eq!(u32_struct_name, "GenericStructForU32"); - - let string_struct_name = type_names.get(&string_struct_id).unwrap(); - assert_eq!(string_struct_name, "GenericStructForStr"); - } - - #[test] - fn generic_variant_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_string_enum_id = registry - .register_type(&MetaType::new::>()) - .id; - let bool_u32_enum_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_string_enum_name = type_names.get(&u32_string_enum_id).unwrap(); - assert_eq!(u32_string_enum_name, "GenericEnumForU32AndStr"); - - let bool_u32_enum_name = type_names.get(&bool_u32_enum_id).unwrap(); - assert_eq!(bool_u32_enum_name, "GenericEnumForBoolAndU32"); - } - - #[test] - fn array_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_array_id = registry.register_type(&MetaType::new::<[u32; 10]>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_array_name = type_names.get(&u32_array_id).unwrap(); - assert_eq!(u32_array_name, "[u32, 10]"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForArrOf10U32"); - } - - #[test] - fn vector_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_vector_id = registry.register_type(&MetaType::new::>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_vector_name = type_names.get(&u32_vector_id).unwrap(); - assert_eq!(u32_vector_name, "vec u32"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForVecOfU32"); - } - - #[test] - fn result_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_result_id = registry - .register_type(&MetaType::new::>()) - .id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_result_name = type_names.get(&u32_result_id).unwrap(); - assert_eq!(u32_result_name, "result (u32, str)"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForResultOfU32OrStr"); - } - - #[test] - fn option_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_option_id = registry.register_type(&MetaType::new::>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_option_name = type_names.get(&u32_option_id).unwrap(); - assert_eq!(u32_option_name, "opt u32"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForOptOfU32"); - } - - #[test] - fn tuple_type_name_resolution_works() { - let mut registry = Registry::new(); - let u32_str_tuple_id = registry.register_type(&MetaType::new::<(u32, String)>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let u32_str_tuple_name = type_names.get(&u32_str_tuple_id).unwrap(); - assert_eq!(u32_str_tuple_name, "struct { u32, str }"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForStructOfU32AndStr"); - } - - #[test] - fn btree_map_type_name_resolution_works() { - let mut registry = Registry::new(); - let btree_map_id = registry - .register_type(&MetaType::new::>()) - .id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let btree_map_name = type_names.get(&btree_map_id).unwrap(); - assert_eq!(btree_map_name, "map (u32, str)"); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!(as_generic_param_name, "GenericStructForMapOfU32ToStr"); - } - - #[test] - fn type_name_minification_works_for_types_with_the_same_mod_depth() { - let mut registry = Registry::new(); - let t1_id = registry.register_type(&MetaType::new::()).id; - let t2_id = registry.register_type(&MetaType::new::()).id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let t1_name = type_names.get(&t1_id).unwrap(); - assert_eq!(t1_name, "Mod1T1"); - - let t2_name = type_names.get(&t2_id).unwrap(); - assert_eq!(t2_name, "Mod2T1"); - } - - #[test] - fn type_name_minification_works_for_types_with_different_mod_depth() { - let mut registry = Registry::new(); - let t1_id = registry - .register_type(&MetaType::new::()) - .id; - let t2_id = registry.register_type(&MetaType::new::()).id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let t1_name = type_names.get(&t1_id).unwrap(); - assert_eq!(t1_name, "Mod1Mod2T2"); - - let t2_name = type_names.get(&t2_id).unwrap(); - assert_eq!(t2_name, "TestsMod2T2"); - } - - macro_rules! type_name_resolution_works { - ($primitive:ident, $alias:ident) => { - let mut registry = Registry::new(); - let id = registry.register_type(&MetaType::new::<$primitive>()).id; - let as_generic_param_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - let name = type_names.get(&id).unwrap(); - assert_eq!(name, stringify!($alias)); - let as_generic_param_name = type_names.get(&as_generic_param_id).unwrap(); - assert_eq!( - as_generic_param_name, - concat!("GenericStructFor", stringify!($primitive)) - ); - }; - } - - #[test] - fn actor_id_type_name_resolution_works() { - type_name_resolution_works!(ActorId, actor_id); - } - - #[test] - fn message_id_type_name_resolution_works() { - type_name_resolution_works!(MessageId, message_id); - } - - #[test] - fn code_id_type_name_resolution_works() { - type_name_resolution_works!(CodeId, code_id); - } - - #[test] - fn h160_type_name_resolution_works() { - type_name_resolution_works!(H160, h160); - } - - #[test] - fn nonzero_u8_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU8, nat8); - } - - #[test] - fn nonzero_u16_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU16, nat16); - } - - #[test] - fn nonzero_u32_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU32, nat32); - } - - #[test] - fn nonzero_u64_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU64, nat64); - } - - #[test] - fn nonzero_u128_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU128, nat128); - } - - #[test] - fn nonzero_u256_type_name_resolution_works() { - type_name_resolution_works!(NonZeroU256, nat256); - } - - #[test] - fn generic_const_struct_type_name_resolution_works() { - let mut registry = Registry::new(); - let n8_id = registry - .register_type(&MetaType::new::>()) - .id; - let n8_id_2 = registry - .register_type(&MetaType::new::>()) - .id; - let n32_id = registry - .register_type(&MetaType::new::>()) - .id; - let n256_id = registry - .register_type(&MetaType::new::>()) - .id; - let n32u256_id = registry - .register_type(&MetaType::new::>()) - .id; - let portable_registry = PortableRegistry::from(registry); - - let type_names = resolve(portable_registry.types.iter()).unwrap(); - - assert_eq!(n8_id, n8_id_2); - assert_ne!(n8_id, n32_id); - assert_ne!(n8_id, n256_id); - assert_eq!(type_names.get(&n8_id).unwrap(), "GenericConstStruct1ForU8"); - assert_eq!(type_names.get(&n32_id).unwrap(), "GenericConstStruct2ForU8"); - assert_eq!( - type_names.get(&n256_id).unwrap(), - "GenericConstStruct3ForU8" - ); - assert_eq!( - type_names.get(&n32u256_id).unwrap(), - "GenericConstStructForU256" - ); - } -} diff --git a/rs/src/builder.rs b/rs/src/builder.rs index a7a412193..d3f866bfa 100644 --- a/rs/src/builder.rs +++ b/rs/src/builder.rs @@ -146,7 +146,7 @@ impl ClientBuilder

{ pub fn with_program_name(self, program_name: &str) -> Self { Self { - program_name: program_name.to_string(), + program_name: program_name.to_case(Case::Snake), ..self } } @@ -165,10 +165,11 @@ impl ClientBuilder

{ /// /// Returns client code generator. pub fn build_idl<'a>(&'a self) -> ClientGenerator<'a, IdlPath<'a>> { + let program_name = self.program_name.to_case(Case::Pascal); let idl_path = self.idl_path.as_ref().expect("idl path not set"); let client_path = self.client_path.as_ref().expect("client path not set"); // Generate IDL file for the program - sails_idl_gen::generate_idl_to_file::

(idl_path.as_path()) + sails_idl_gen::generate_idl_to_file::

(Some(program_name.as_str()), idl_path.as_path()) .expect("Error generating IDL from program"); ClientGenerator::from_idl_path(idl_path.as_path()).with_client_path(client_path.as_path()) @@ -180,10 +181,14 @@ impl ClientBuilder

{ /// - first `Option` is path to the IDL file if generated. /// - second `Option` is path to the client file if generated. pub fn build(self) -> (Option, Option) { + let program_name = self.program_name.to_case(Case::Pascal); if let Some(idl_path) = self.idl_path.as_ref() { // Generate IDL file for the program - sails_idl_gen::generate_idl_to_file::

(idl_path.as_path()) - .expect("Error generating IDL from program"); + sails_idl_gen::generate_idl_to_file::

( + Some(program_name.as_str()), + idl_path.as_path(), + ) + .expect("Error generating IDL from program"); if let Some(client_path) = self.client_path.as_ref() { // Generate client code from IDL file @@ -195,16 +200,14 @@ impl ClientBuilder

{ } else if let Some(client_path) = self.client_path.as_ref() { // Generate IDL string for the program let mut idl = Vec::new(); - sails_idl_gen::generate_idl::

(&mut idl).expect("Error generating IDL from program"); + sails_idl_gen::generate_idl::

(Some(program_name.as_str()), &mut idl) + .expect("Error generating IDL from program"); let idl = String::from_utf8(idl).unwrap(); // Generate client code from IDL string ClientGenerator::from_idl(&idl) .with_no_std(self.no_std) - .generate_to( - self.program_name.to_case(Case::Pascal).as_str(), - client_path.as_path(), - ) + .generate_to(client_path.as_path()) .expect("Error generating client from IDL"); } From 273508e10bce216992f7f0586b936f8254d2806f Mon Sep 17 00:00:00 2001 From: vobradovich Date: Mon, 15 Dec 2025 18:16:32 +0100 Subject: [PATCH 11/16] fix: gemini comment --- rs/idl-gen/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index ba62a6fd4..2a20ec7eb 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -56,15 +56,12 @@ pub mod service { Ok(()) } - pub fn generate_idl_to_file(path: impl AsRef) -> Result<()> { - let service_name = path - .as_ref() - .file_name() - .map(|f| f.to_string_lossy().to_string()) - .unwrap_or_else(|| "Service".to_string()); - + pub fn generate_idl_to_file( + service_name: &str, + path: impl AsRef, + ) -> Result<()> { let mut idl_new_content = Vec::new(); - generate_idl::(service_name.as_str(), &mut idl_new_content)?; + generate_idl::(service_name, &mut idl_new_content)?; if let Ok(idl_old_content) = fs::read(&path) && idl_new_content == idl_old_content { From b16a8750dd9a9cce844f65b5b63ea16cdb2c71ab Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 16 Dec 2025 16:15:07 +0100 Subject: [PATCH 12/16] wip: make idl-gen `no_std` --- Cargo.lock | 16 ---------- Cargo.toml | 4 +-- rs/Cargo.toml | 2 +- rs/cli/Cargo.toml | 2 +- rs/idl-gen/Cargo.toml | 7 +++-- rs/idl-gen/src/builder.rs | 5 ++-- rs/idl-gen/src/errors.rs | 5 ++++ rs/idl-gen/src/generic_resolver.rs | 1 - rs/idl-gen/src/lib.rs | 47 +++++++++++++++++++++--------- rs/idl-gen/src/type_resolver.rs | 17 +++++------ rs/idl-gen/tests/generator.rs | 37 +++++++++++------------ rs/idl-meta/Cargo.toml | 2 +- rs/src/builder.rs | 4 +-- 13 files changed, 75 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18c573a6c..f9d76a396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,13 +562,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" dependencies = [ "askama_parser", - "basic-toml", "memchr", "proc-macro2", "quote", "rustc-hash 2.1.1", - "serde", - "serde_derive", "syn 2.0.104", ] @@ -579,8 +576,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" dependencies = [ "memchr", - "serde", - "serde_derive", "winnow 0.7.1", ] @@ -806,15 +801,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic-toml" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] - [[package]] name = "beef" version = "0.5.2" @@ -7237,8 +7223,6 @@ dependencies = [ "sails-idl-meta", "sails-idl-parser-v2", "scale-info", - "serde", - "serde_json", "syn 2.0.104", "thiserror 2.0.12", ] diff --git a/Cargo.toml b/Cargo.toml index 545d380ce..b2d99a478 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ members = [ # The order matches with publishing order. sails-idl-meta = { path = "rs/idl-meta" } sails-idl-parser = { path = "rs/idl-parser" } -sails-idl-parser-v2 = { path = "rs/idl-parser-v2" } +sails-idl-parser-v2 = { path = "rs/idl-parser-v2", default-features = false } sails-idl-gen = { path = "rs/idl-gen" } sails-client-gen = { path = "rs/client-gen" } sails-macros-core = { path = "rs/macros/core" } @@ -75,7 +75,7 @@ gwasm-builder = { version = "=1.9.2", package = "gear-wasm-builder" } alloy-primitives = { version = "0.8.19", default-features = false } alloy-sol-types = { version = "0.8.19", default-features = false } anyhow = "1" -askama = "0.14" +askama = { version = "0.14", default-features = false } cargo-generate = "0.23" cargo_metadata = "0.19" clap = "4.5" diff --git a/rs/Cargo.toml b/rs/Cargo.toml index b8e6ebe1c..e4537943d 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -64,5 +64,5 @@ gtest = ["std", "dep:gtest", "dep:log", "dep:tokio-stream"] idl-gen = ["dep:sails-idl-gen"] client-builder = ["std", "idl-gen", "dep:sails-client-gen", "dep:convert_case"] mockall = ["std", "dep:mockall"] -std = ["futures/std"] +std = ["futures/std", "sails-idl-gen?/std"] wasm-builder = ["dep:gwasm-builder"] diff --git a/rs/cli/Cargo.toml b/rs/cli/Cargo.toml index 97542cfba..fa117a1a5 100644 --- a/rs/cli/Cargo.toml +++ b/rs/cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] anyhow.workspace = true -askama.workspace = true +askama = { workspace = true, features = ["std"] } cargo-generate.workspace = true cargo_metadata.workspace = true clap = { workspace = true, features = ["derive"] } diff --git a/rs/idl-gen/Cargo.toml b/rs/idl-gen/Cargo.toml index 06ef484cb..311cb3017 100644 --- a/rs/idl-gen/Cargo.toml +++ b/rs/idl-gen/Cargo.toml @@ -10,17 +10,18 @@ repository.workspace = true rust-version.workspace = true [dependencies] -askama.workspace = true +askama = { workspace = true, features = ["alloc", "derive"] } convert_case.workspace = true gprimitives.workspace = true sails-idl-meta = { workspace = true, features = ["ast", "templates"] } scale-info = { workspace = true, features = ["derive", "docs", "serde"] } -serde = { workspace = true, features = ["derive"] } -serde-json.workspace = true thiserror.workspace = true syn = { workspace = true, features = ["parsing", "full", "fold"] } quote.workspace = true +[features] +std = [] + [dev-dependencies] insta.workspace = true sails-idl-parser-v2.workspace = true diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 1e097c9d4..2bf96d585 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -1,7 +1,6 @@ use super::*; use crate::type_resolver::TypeResolver; use scale_info::*; -use std::collections::HashSet; pub struct ProgramBuilder { registry: PortableRegistry, @@ -83,7 +82,7 @@ impl ProgramBuilder { } pub fn build(self, name: String) -> Result { - let mut exclude = HashSet::new(); + let mut exclude = BTreeSet::new(); exclude.insert(self.ctors_type_id); exclude.extend(any_funcs_ids(&self.registry, self.ctors_type_id)?); let resolver = TypeResolver::try_from(&self.registry, exclude)?; @@ -167,7 +166,7 @@ impl<'a> ServiceBuilder<'a> { services.extend(ServiceBuilder::new(name, meta).build()?); } - let exclude = HashSet::from_iter(self.exclude_type_ids()?); + let exclude = BTreeSet::from_iter(self.exclude_type_ids()?); let resolver = TypeResolver::try_from(&self.registry, exclude)?; let commands = self.commands(&resolver)?; let queries = self.queries(&resolver)?; diff --git a/rs/idl-gen/src/errors.rs b/rs/idl-gen/src/errors.rs index cb8df4b1b..3c4056005 100644 --- a/rs/idl-gen/src/errors.rs +++ b/rs/idl-gen/src/errors.rs @@ -1,3 +1,5 @@ +use super::*; + pub type Result = core::result::Result; #[derive(thiserror::Error, Debug)] @@ -13,5 +15,8 @@ pub enum Error { #[error("type `{0}` is not supported")] TypeIsUnsupported(String), #[error(transparent)] + Template(#[from] askama::Error), + #[cfg(feature = "std")] + #[error(transparent)] Io(#[from] std::io::Error), } diff --git a/rs/idl-gen/src/generic_resolver.rs b/rs/idl-gen/src/generic_resolver.rs index b4839dfc2..0f0a50e8c 100644 --- a/rs/idl-gen/src/generic_resolver.rs +++ b/rs/idl-gen/src/generic_resolver.rs @@ -1,5 +1,4 @@ use super::*; -use std::collections::BTreeSet; pub(crate) fn resolve_generic_type_decl( type_decl: &TypeDecl, diff --git a/rs/idl-gen/src/lib.rs b/rs/idl-gen/src/lib.rs index 2a20ec7eb..0ea0ba54d 100644 --- a/rs/idl-gen/src/lib.rs +++ b/rs/idl-gen/src/lib.rs @@ -1,9 +1,23 @@ +#![no_std] + +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + format, + string::{String, ToString as _}, + vec, + vec::Vec, +}; use askama::Template; pub use errors::*; pub use program::*; use sails_idl_meta::*; use scale_info::{Variant, form::PortableForm}; -use std::{fs, io::Write, path::Path}; mod builder; mod errors; @@ -18,28 +32,29 @@ pub mod program { pub fn generate_idl( program_name: Option<&str>, - mut idl_writer: impl Write, + mut idl_writer: impl core::fmt::Write, ) -> Result<()> { let doc = build_program_ast::

(program_name)?; - doc.write_into(&mut idl_writer)?; + doc.render_into(&mut idl_writer)?; Ok(()) } + #[cfg(feature = "std")] pub fn generate_idl_to_file( program_name: Option<&str>, - path: impl AsRef, + path: impl AsRef, ) -> Result<()> { - let mut idl_new_content = Vec::new(); + let mut idl_new_content = String::new(); generate_idl::

(program_name, &mut idl_new_content)?; - if let Ok(idl_old_content) = fs::read(&path) + if let Ok(idl_old_content) = std::fs::read_to_string(&path) && idl_new_content == idl_old_content { return Ok(()); } if let Some(dir_path) = path.as_ref().parent() { - fs::create_dir_all(dir_path)?; + std::fs::create_dir_all(dir_path)?; } - Ok(fs::write(&path, idl_new_content)?) + Ok(std::fs::write(&path, idl_new_content)?) } } @@ -49,25 +64,29 @@ pub mod service { pub fn generate_idl( service_name: &str, - mut idl_writer: impl Write, + mut idl_writer: impl core::fmt::Write, ) -> Result<()> { let doc = build_service_ast(service_name, AnyServiceMeta::new::())?; - doc.write_into(&mut idl_writer)?; + doc.render_into(&mut idl_writer)?; Ok(()) } + #[cfg(feature = "std")] pub fn generate_idl_to_file( service_name: &str, - path: impl AsRef, + path: impl AsRef, ) -> Result<()> { - let mut idl_new_content = Vec::new(); + let mut idl_new_content = String::new(); generate_idl::(service_name, &mut idl_new_content)?; - if let Ok(idl_old_content) = fs::read(&path) + if let Ok(idl_old_content) = std::fs::read_to_string(&path) && idl_new_content == idl_old_content { return Ok(()); } - Ok(fs::write(&path, idl_new_content)?) + if let Some(dir_path) = path.as_ref().parent() { + std::fs::create_dir_all(dir_path)?; + } + Ok(std::fs::write(&path, idl_new_content)?) } } diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index a0a761ad3..ac1a8e628 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -4,13 +4,12 @@ use scale_info::{ Field, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, TypeDefPrimitive, TypeDefVariant, form::PortableForm, }; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; #[derive(Debug, Clone)] pub struct TypeResolver<'a> { registry: &'a PortableRegistry, - map: HashMap, - user_defined: HashMap, + map: BTreeMap, + user_defined: BTreeMap, } #[derive(Debug, Clone)] @@ -52,14 +51,14 @@ impl UserDefinedEntry { impl<'a> TypeResolver<'a> { #[cfg(test)] pub fn from_registry(registry: &'a PortableRegistry) -> Self { - TypeResolver::try_from(registry, HashSet::new()).unwrap() + TypeResolver::try_from(registry, BTreeSet::new()).unwrap() } - pub fn try_from(registry: &'a PortableRegistry, exclude: HashSet) -> Result { + pub fn try_from(registry: &'a PortableRegistry, exclude: BTreeSet) -> Result { let mut resolver = Self { registry, - map: HashMap::new(), - user_defined: HashMap::new(), + map: BTreeMap::new(), + user_defined: BTreeMap::new(), }; resolver.build_type_decl_map(exclude)?; Ok(resolver) @@ -79,7 +78,7 @@ impl<'a> TypeResolver<'a> { self.map.get(&key) } - fn build_type_decl_map(&mut self, exclude: HashSet) -> Result<()> { + fn build_type_decl_map(&mut self, exclude: BTreeSet) -> Result<()> { let filtered: Vec<_> = self .registry .types @@ -415,9 +414,9 @@ fn possible_names_by_path(ty: &Type) -> impl Iterator( Some("TestProgramWithEmptyCtorsMeta"), &mut idl, ) .unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - insta::assert_snapshot!(generated_idl); + insta::assert_snapshot!(idl); - let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); assert!(generated_idl_program.program.is_some()); let program = generated_idl_program.program.unwrap(); assert!(program.ctors.is_empty()); @@ -288,17 +287,16 @@ fn program_idl_works_with_empty_ctors() { #[test] fn program_idl_works_with_non_empty_ctors() { - let mut idl = Vec::new(); + let mut idl = String::new(); program::generate_idl::( Some("TestProgramWithNonEmptyCtorsMeta"), &mut idl, ) .unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - insta::assert_snapshot!(generated_idl); + insta::assert_snapshot!(idl); - let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); assert!(generated_idl_program.program.is_some()); let program = generated_idl_program.program.unwrap(); assert_eq!(program.ctors.len(), 2); @@ -309,17 +307,16 @@ fn program_idl_works_with_non_empty_ctors() { #[test] fn program_idl_works_with_multiple_services() { - let mut idl = Vec::new(); + let mut idl = String::new(); program::generate_idl::( Some("TestProgramWithMultipleServicesMeta"), &mut idl, ) .unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - insta::assert_snapshot!(generated_idl); + insta::assert_snapshot!(idl); - let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); assert!(generated_idl_program.program.is_some()); let program = generated_idl_program.program.unwrap(); assert!(program.ctors.is_empty()); @@ -335,12 +332,12 @@ fn program_idl_works_with_multiple_services() { #[test] fn service_idl_works_with_basics() { - let mut idl = Vec::new(); + let mut idl = String::new(); service::generate_idl::("TestServiceMeta", &mut idl).unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - insta::assert_snapshot!(generated_idl); - let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); assert!(generated_idl_program.program.is_none()); assert_eq!(generated_idl_program.services.len(), 1); assert_eq!(generated_idl_program.services[0].funcs.len(), 4); @@ -349,7 +346,7 @@ fn service_idl_works_with_basics() { #[test] fn service_idl_works_with_base_services() { - let mut idl = Vec::new(); + let mut idl = String::new(); service::generate_idl::< ServiceMetaWithBase< CommandsMeta, @@ -359,10 +356,10 @@ fn service_idl_works_with_base_services() { >, >("ServiceMetaWithBase", &mut idl) .unwrap(); - let generated_idl = String::from_utf8(idl).unwrap(); - insta::assert_snapshot!(generated_idl); - let generated_idl_program = sails_idl_parser_v2::parse_idl(&generated_idl).unwrap(); + insta::assert_snapshot!(idl); + + let generated_idl_program = sails_idl_parser_v2::parse_idl(&idl).unwrap(); assert!(generated_idl_program.program.is_none()); assert_eq!(generated_idl_program.services.len(), 2); assert_eq!(generated_idl_program.services[0].funcs.len(), 4); diff --git a/rs/idl-meta/Cargo.toml b/rs/idl-meta/Cargo.toml index 10bd7c7d3..9ef569e02 100644 --- a/rs/idl-meta/Cargo.toml +++ b/rs/idl-meta/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -askama = { workspace = true, optional = true } +askama = { workspace = true, optional = true, features = ["alloc", "derive"] } scale-info.workspace = true [dev-dependencies] diff --git a/rs/src/builder.rs b/rs/src/builder.rs index d3f866bfa..b67d29919 100644 --- a/rs/src/builder.rs +++ b/rs/src/builder.rs @@ -7,7 +7,6 @@ use std::{ ffi::OsStr, path::{Path, PathBuf}, string::{String, ToString}, - vec::Vec, }; /// Shorthand function to be used in `build.rs`. @@ -199,10 +198,9 @@ impl ClientBuilder

{ } } else if let Some(client_path) = self.client_path.as_ref() { // Generate IDL string for the program - let mut idl = Vec::new(); + let mut idl = String::new(); sails_idl_gen::generate_idl::

(Some(program_name.as_str()), &mut idl) .expect("Error generating IDL from program"); - let idl = String::from_utf8(idl).unwrap(); // Generate client code from IDL string ClientGenerator::from_idl(&idl) From 185cf9a62f26bb587f8a0da7278fc4899c019c6e Mon Sep 17 00:00:00 2001 From: vobradovich Date: Tue, 16 Dec 2025 19:07:51 +0100 Subject: [PATCH 13/16] wip: builder tests --- rs/ethexe/Cargo.lock | 16 - rs/idl-gen/src/builder.rs | 1895 ++++++++++++++++- ...gram_idl_works_with_multiple_services.snap | 4 +- rs/idl-meta/src/ast.rs | 1 + rs/idl-meta/src/lib.rs | 6 + 5 files changed, 1898 insertions(+), 24 deletions(-) diff --git a/rs/ethexe/Cargo.lock b/rs/ethexe/Cargo.lock index af98b1c35..2b0845c0f 100644 --- a/rs/ethexe/Cargo.lock +++ b/rs/ethexe/Cargo.lock @@ -462,13 +462,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" dependencies = [ "askama_parser", - "basic-toml", "memchr", "proc-macro2", "quote", "rustc-hash", - "serde", - "serde_derive", "syn 2.0.100", ] @@ -479,8 +476,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" dependencies = [ "memchr", - "serde", - "serde_derive", "winnow 0.7.3", ] @@ -545,15 +540,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic-toml" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] - [[package]] name = "bincode" version = "1.3.3" @@ -4243,8 +4229,6 @@ dependencies = [ "quote", "sails-idl-meta", "scale-info", - "serde", - "serde_json", "syn 2.0.100", "thiserror 2.0.12", ] diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 2bf96d585..7d8ae30e2 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -10,15 +10,27 @@ pub struct ProgramBuilder { impl ProgramBuilder { pub fn new() -> Self { - let ctors = P::constructors(); + let mut service_ids: BTreeMap = BTreeMap::new(); let mut registry = Registry::new(); + let ctors = P::constructors(); let ctors_type_id = registry.register_type(&ctors).id; let services_expo = P::services() - .map(|(name, _meta)| ServiceExpo { - name: name.to_string(), - route: None, - docs: vec![], - annotations: vec![], + .map(|(service_name, meta)| { + // TEMP: dedup by interface_id + // will not be needed after routring by interface_id + let key = u64::from_le_bytes(meta.interface_id().clone()); + let (name, route) = if let Some(name) = service_ids.get(&key) { + (name.to_string(), Some(service_name.to_string())) + } else { + service_ids.insert(key, service_name); + (service_name.to_string(), None) + }; + ServiceExpo { + name, + route, + docs: vec![], + annotations: vec![], + } }) .collect::>(); let registry = PortableRegistry::from(registry); @@ -360,3 +372,1874 @@ impl<'a> ServiceBuilder<'a> { .collect() } } + +#[cfg(test)] +mod tests { + use super::*; + use core::marker::PhantomData; + use core::num::NonZeroU128; + use gprimitives::{ActorId, CodeId, H160, H256, MessageId, NonZeroU256, U256}; + use scale_info::TypeInfo; + + mod utils { + use super::*; + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum SimpleCtors { + SimpleCtor(SimpleCtorParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) struct SimpleCtorParams { + f1: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) struct SimpleFunctionParams { + f1: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum NoCommands {} + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum NoQueries {} + + #[derive(TypeInfo)] + #[allow(unused)] + pub(super) enum NoEvents {} + } + + fn test_program_unit() -> Result { + struct TestProgram(PhantomData); + impl ProgramMeta for TestProgram { + type ConstructorsMeta = C; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + } + + ProgramBuilder::new::>().build("TestProgram".to_string()) + } + + fn test_service_units(service_name: &'static str) -> Result> { + let meta = AnyServiceMeta::new::(); + ServiceBuilder::new(service_name, &meta).build() + } + + // ------------------------------------------------------------------------------------ + // -------------------------- Program section related tests --------------------------- + // ------------------------------------------------------------------------------------ + + /// Test various constructor validation errors + #[test] + fn ctor_validation_errors() { + // Define all constructor error test types + #[derive(TypeInfo)] + #[allow(unused)] + enum NonCompositeArgsCtors { + CtorWithInvalidArgTypes(u32), // u32 is not composite, should cause error + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum NamelessFieldsCtors { + CtorWithNamelessArgs(NamelessFieldParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NamelessFieldParams(u32, String); + + #[derive(TypeInfo)] + #[allow(unused)] + enum NoArgsCtors { + CtorWithNoArgs, + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum TooManyArgsCtors { + CtorWithResult(ValidParams, String), // Should have exactly 1 field, not 2 + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ValidParams { + pub param1: u32, + } + + // Helper function to test constructor validation errors + fn test_ctor_error(expected_error_msg: &str) { + let result = test_program_unit::(); + + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!(msg.as_str(), expected_error_msg); + } + + // Test all error scenarios + test_ctor_error::( + "ctor `CtorWithInvalidArgTypes` params type is not a composite", + ); + + test_ctor_error::( + "ctor `CtorWithNamelessArgs` param is missing a name", + ); + + test_ctor_error::("func `CtorWithNoArgs` has no fields"); + + test_ctor_error::("ctor `CtorWithResult` has invalid number of fields"); + } + + /// Test that returned program meta has result_ty == None for all constructors in program IDL section + #[test] + fn ctors_build_works() { + #[derive(TypeInfo)] + #[allow(unused)] + enum ValidConstructors { + Zero(ZeroParams), + One(OneParam), + Three(ThreeParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ZeroParams {} + + #[derive(TypeInfo)] + #[allow(unused)] + struct OneParam { + pub actor: ActorId, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ThreeParams { + pub code: CodeId, + pub name: String, + pub num: NonZeroU256, + } + + let meta = test_program_unit::().expect("ProgramBuilder error"); + + // Check that all constructors have parsed + assert_eq!(meta.ctors.len(), 3); + } + + /// Test successful creation with valid constructors and services + #[test] + fn ctor_simple_positive_test() { + use TypeDecl::*; + + #[derive(TypeInfo)] + #[allow(unused)] + enum Ctors { + Ctor(InitParams), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct InitParams { + pub initial_value: u32, + } + + let meta = test_program_unit::().expect("ProgramBuilder error"); + + assert_eq!( + meta.ctors, + vec![CtorFunc { + name: "Ctor".to_string(), + params: vec![FuncParam { + name: "initial_value".to_string(), + type_decl: Primitive(PrimitiveType::U32) + }], + docs: vec![], + annotations: vec![] + }] + ); + } + + #[test] + fn program_has_services() { + struct TestService; + impl ServiceMeta for TestService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, sails_idl_meta::AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 0u64.to_le_bytes(); + } + + struct TestProgram; + impl ProgramMeta for TestProgram { + type ConstructorsMeta = utils::SimpleCtors; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("TestService1", AnyServiceMeta::new::), + ("TestService2", AnyServiceMeta::new::), + ("TestService3", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + } + + let meta = ProgramBuilder::new::() + .build("TestProgram".to_string()) + .expect("ProgramBuilder error"); + + assert_eq!(meta.services.len(), 3); + assert_eq!( + meta.services[0], + ServiceExpo { + name: "TestService1".to_string(), + route: None, + docs: vec![], + annotations: vec![] + } + ); + assert_eq!( + meta.services[1], + ServiceExpo { + name: "TestService1".to_string(), + route: Some("TestService2".to_string()), + docs: vec![], + annotations: vec![] + } + ); + assert_eq!( + meta.services[2], + ServiceExpo { + name: "TestService1".to_string(), + route: Some("TestService3".to_string()), + docs: vec![], + annotations: vec![] + } + ); + } + + #[test] + #[ignore = "TODO"] + fn program_has_same_name_services() { + struct TestService; + impl ServiceMeta for TestService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 0u64.to_le_bytes(); + } + + struct TestProgram; + impl ProgramMeta for TestProgram { + type ConstructorsMeta = utils::SimpleCtors; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("TestService", AnyServiceMeta::new::), + ("TestService", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + } + + let meta = ProgramBuilder::new::() + .build("TestProgram".to_string()) + .expect("ProgramBuilder error"); + + assert_eq!(meta.services.len(), 2); + assert_eq!( + meta.services[0], + ServiceExpo { + name: "TestService".to_string(), + route: None, + docs: vec![], + annotations: vec![] + } + ); + assert_eq!( + meta.services[1], + ServiceExpo { + name: "TestService".to_string(), + route: Some("TestService".to_string()), + docs: vec![], + annotations: vec![] + } + ); + } + + #[test] + fn program_section_has_types_section() { + #[derive(TypeInfo)] + #[allow(unused)] + enum Ctors { + Ctor1(Ctor1Params), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct Ctor1Params { + pub param1: u32, + pub param2: ActorId, + pub param3: CtorType, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct CtorType(String); + + let meta = test_program_unit::().expect("ProgramBuilder error"); + + assert_eq!(meta.types.len(), 1); + assert!(matches!( + meta.types.get(0), + Some(sails_idl_meta::Type { name, .. }) if name == "CtorType" + )); + } + + // ------------------------------------------------------------------------------------ + // -------------------- Extension and base services related tests --------------------- + // ------------------------------------------------------------------------------------ + + #[test] + fn base_service_entities_doesnt_automatically_occur() { + struct BaseServiceMeta; + impl ServiceMeta for BaseServiceMeta { + type CommandsMeta = BaseServiceCommands; + type QueriesMeta = BaseServiceQueries; + type EventsMeta = BaseServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 1u64.to_le_bytes(); + } + + struct ExtendedServiceMeta; + impl ServiceMeta for ExtendedServiceMeta { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = ExtendedServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseServiceMeta", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 2u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceCommands { + BaseCmd(BaseServiceFunctionParams, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceQueries { + BaseQuery(BaseServiceFunctionParams, ActorId), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceEvents { + BaseEvent(NonZeroU128), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct BaseServiceFunctionParams { + param: SomeBaseServiceType, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct SomeBaseServiceType(ActorId); + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceEvents { + ExtendedEvent(SomeExtendedServiceType), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct SomeExtendedServiceType(CodeId); + + let services = test_service_units::("ExtendedService") + .expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let extended_service = &services[1]; + + assert_eq!(base_service.name.as_str(), "BaseServiceMeta"); + assert_eq!(extended_service.name.as_str(), "ExtendedService"); + + assert_eq!( + extended_service.extends, + vec!["BaseServiceMeta".to_string()] + ); + + assert_eq!(base_service.funcs.len(), 2); + assert!( + base_service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Command && f.name == "BaseCmd") + ); + assert!( + base_service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Query && f.name == "BaseQuery") + ); + + assert!(extended_service.funcs.is_empty()); + + let base_events: Vec<&str> = base_service + .events + .iter() + .map(|e| e.name.as_str()) + .collect(); + let extended_events: Vec<&str> = extended_service + .events + .iter() + .map(|e| e.name.as_str()) + .collect(); + assert_eq!(base_events, vec!["BaseEvent"]); + assert_eq!(extended_events, vec!["ExtendedEvent"]); + + let base_types: Vec<&str> = base_service.types.iter().map(|t| t.name.as_str()).collect(); + let extended_types: Vec<&str> = extended_service + .types + .iter() + .map(|t| t.name.as_str()) + .collect(); + assert_eq!(base_types, vec!["NonZeroU128", "SomeBaseServiceType"]); + assert_eq!(extended_types, vec!["SomeExtendedServiceType"]); + } + + #[test] + fn service_extension_with_conflicting_names() { + struct BaseServiceMeta; + impl ServiceMeta for BaseServiceMeta { + type CommandsMeta = BaseServiceCommands; + type QueriesMeta = BaseServiceQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 10u64.to_le_bytes(); + } + + struct ExtendedServiceMeta; + impl ServiceMeta for ExtendedServiceMeta { + type CommandsMeta = ExtendedServiceCommands; + type QueriesMeta = ExtendedServiceQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 11u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceCommands { + ConflictingCmd(utils::SimpleFunctionParams, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceQueries { + ConflictingQuery(utils::SimpleFunctionParams, u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceCommands { + ConflictingCmd(utils::SimpleFunctionParams, bool), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceQueries { + ConflictingQuery(utils::SimpleFunctionParams, String), + } + + let services = test_service_units::("ExtendedService") + .expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let extended_service = &services[1]; + + assert_eq!(base_service.name.as_str(), "BaseService"); + assert_eq!(extended_service.name.as_str(), "ExtendedService"); + + let base_cmd = base_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Command && f.name == "ConflictingCmd") + .expect("missing base command"); + let extended_cmd = extended_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Command && f.name == "ConflictingCmd") + .expect("missing extended command"); + + assert_eq!(base_cmd.output, TypeDecl::Primitive(PrimitiveType::String)); + assert_eq!( + extended_cmd.output, + TypeDecl::Primitive(PrimitiveType::Bool) + ); + + let base_query = base_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Query && f.name == "ConflictingQuery") + .expect("missing base query"); + let extended_query = extended_service + .funcs + .iter() + .find(|f| f.kind == FunctionKind::Query && f.name == "ConflictingQuery") + .expect("missing extended query"); + + assert_eq!(base_query.output, TypeDecl::Primitive(PrimitiveType::U32)); + assert_eq!( + extended_query.output, + TypeDecl::Primitive(PrimitiveType::String) + ); + } + + #[test] + fn service_extension_with_conflicting_events() { + struct BaseService; + impl ServiceMeta for BaseService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = BaseServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 20u64.to_le_bytes(); + } + + struct ExtendedService; + impl ServiceMeta for ExtendedService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = ExtendedServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 21u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BaseServiceEvents { + ConflictingEvent(u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ExtendedServiceEvents { + ConflictingEvent(String), + } + + let services = + test_service_units::("ExtendedService").expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let extended_service = &services[1]; + + assert_eq!(base_service.name.as_str(), "BaseService"); + assert_eq!(extended_service.name.as_str(), "ExtendedService"); + + let base_event = base_service + .events + .iter() + .find(|e| e.name == "ConflictingEvent") + .expect("missing base event"); + let extended_event = extended_service + .events + .iter() + .find(|e| e.name == "ConflictingEvent") + .expect("missing extended event"); + + assert_eq!(base_event.def.fields.len(), 1); + assert_eq!( + base_event.def.fields[0].type_decl, + TypeDecl::Primitive(PrimitiveType::U32) + ); + + assert_eq!(extended_event.def.fields.len(), 1); + assert_eq!( + extended_event.def.fields[0].type_decl, + TypeDecl::Primitive(PrimitiveType::String) + ); + } + + #[test] + fn service_extension_with_conflicting_types() { + struct ServiceBase; + impl ServiceMeta for ServiceBase { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = BaseServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 30u64.to_le_bytes(); + } + + #[allow(unused)] + #[derive(TypeInfo)] + enum BaseServiceEvents { + BaseEvent(GenericConstStruct<8>), + } + + struct ExtensionService; + impl ServiceMeta for ExtensionService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = ExtendedServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("ServiceBase", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 31u64.to_le_bytes(); + } + + #[allow(unused)] + #[derive(TypeInfo)] + enum ExtendedServiceEvents { + ExtEvent(GenericConstStruct<16>), + } + + #[allow(unused)] + #[derive(TypeInfo)] + struct GenericConstStruct([u8; N]); + + let services = test_service_units::("ExtensionService") + .expect("ServiceBuilder error"); + + assert_eq!(services.len(), 2); + let base_service = &services[0]; + let ext_service = &services[1]; + + assert_eq!(base_service.types.len(), 1); + assert_eq!(ext_service.types.len(), 1); + + let base_ty = &base_service.types[0]; + assert!(base_ty.name.starts_with("GenericConstStruct")); + let sails_idl_meta::TypeDef::Struct(base_struct_def) = &base_ty.def else { + panic!("expected struct type"); + }; + assert_eq!(base_struct_def.fields.len(), 1); + assert_eq!(base_struct_def.fields[0].type_decl.to_string(), "[u8; 8]"); + + let ext_ty = &ext_service.types[0]; + assert!(ext_ty.name.starts_with("GenericConstStruct")); + let sails_idl_meta::TypeDef::Struct(ext_struct_def) = &ext_ty.def else { + panic!("expected struct type"); + }; + assert_eq!(ext_struct_def.fields.len(), 1); + assert_eq!(ext_struct_def.fields[0].type_decl.to_string(), "[u8; 16]"); + } + + #[test] + fn service_extension_order() { + struct ServiceA1; + impl ServiceMeta for ServiceA1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 40u64.to_le_bytes(); + } + + struct ServiceA2; + impl ServiceMeta for ServiceA2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 41u64.to_le_bytes(); + } + + struct ServiceB2; + impl ServiceMeta for ServiceB2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("ServiceA1", AnyServiceMeta::new::), + ("ServiceA2", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 42u64.to_le_bytes(); + } + + struct ServiceB1; + impl ServiceMeta for ServiceB1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 43u64.to_le_bytes(); + } + + struct ServiceC; + impl ServiceMeta for ServiceC { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("ServiceB1", AnyServiceMeta::new::), + ("ServiceB2", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 44u64.to_le_bytes(); + } + + let services = test_service_units::("ServiceC").expect("ServiceBuilder error"); + + assert_eq!(services.len(), 5); + let names: Vec<&str> = services.iter().map(|s| s.name.as_str()).collect(); + assert_eq!( + names, + vec![ + "ServiceB1", + "ServiceA1", + "ServiceA2", + "ServiceB2", + "ServiceC" + ] + ); + } + + #[test] + #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + fn no_repeated_base_services() { + struct BaseService; + impl ServiceMeta for BaseService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 50u64.to_le_bytes(); + } + + struct Service1; + impl ServiceMeta for Service1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 51u64.to_le_bytes(); + } + + struct Service2; + impl ServiceMeta for Service2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 52u64.to_le_bytes(); + } + + struct TestProgram; + impl ProgramMeta for TestProgram { + type ConstructorsMeta = utils::SimpleCtors; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("Service1", AnyServiceMeta::new::), + ("Service2", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + } + + let doc = build_program_ast::(None).unwrap(); + assert_eq!(doc.services.len(), 3); + } + + #[test] + #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + fn no_repeated_base_services_with_renaming() { + struct BaseService; + impl ServiceMeta for BaseService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 60u64.to_le_bytes(); + } + + struct Service1; + impl ServiceMeta for Service1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("BaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 61u64.to_le_bytes(); + } + + struct Service2; + impl ServiceMeta for Service2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + &[("RenamedBaseService", AnyServiceMeta::new::)]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 62u64.to_le_bytes(); + } + + struct TestProgram; + impl ProgramMeta for TestProgram { + type ConstructorsMeta = utils::SimpleCtors; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("Service1", AnyServiceMeta::new::), + ("Service2", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + } + + let doc = build_program_ast::(None).unwrap(); + assert_eq!(doc.services.len(), 3); + } + + #[test] + #[ignore = "TODO [future]: Must be error when Sails binary protocol is implemented"] + fn no_same_service_in_base_services() { + struct ServiceA; + impl ServiceMeta for ServiceA { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 70u64.to_le_bytes(); + } + + struct ServiceB; + impl ServiceMeta for ServiceB { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("ServiceA", AnyServiceMeta::new::), + ("ServiceA", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 71u64.to_le_bytes(); + } + + assert!(test_service_units::("ServiceB").is_err()); + + struct ServiceC; + impl ServiceMeta for ServiceC { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("ServiceA", AnyServiceMeta::new::), + ("RenamedServiceA", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 72u64.to_le_bytes(); + } + + assert!(test_service_units::("ServiceC").is_err()); + } + + // ------------------------------------------------------------------------------------ + // ------------------------------ Events related tests -------------------------------- + // ------------------------------------------------------------------------------------ + + #[test] + fn invalid_events_type() { + struct InvalidEventsService; + impl ServiceMeta for InvalidEventsService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = InvalidEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 80u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct InvalidEvents { + pub field: u32, + } + + let res = test_service_units::("InvalidEventsService"); + + assert!(res.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = res else { + panic!("Expected FuncMetaIsInvalid error, got {res:?}"); + }; + assert!(msg.contains("references a type that is not a variant")); + } + + #[test] + fn service_events_positive_test() { + use TypeDecl::*; + + struct EventService; + impl ServiceMeta for EventService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = EventServiceEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 81u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum EventServiceEvents { + Zero, + One(u32), + Two(EventTwoParams), + Three { field1: ActorId, field2: String }, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct EventTwoParams { + pub field1: ActorId, + pub field2: String, + } + + let services = + test_service_units::("EventService").expect("ServiceBuilder error"); + + assert_eq!(services.len(), 1); + let service = &services[0]; + + assert_eq!( + service.events, + vec![ + ServiceEvent { + name: "Zero".to_string(), + def: StructDef { fields: vec![] }, + docs: vec![], + annotations: vec![], + }, + ServiceEvent { + name: "One".to_string(), + def: StructDef { + fields: vec![StructField { + name: None, + type_decl: Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }], + }, + docs: vec![], + annotations: vec![], + }, + ServiceEvent { + name: "Two".to_string(), + def: StructDef { + fields: vec![StructField { + name: None, + type_decl: TypeDecl::named("EventTwoParams".to_string()), + docs: vec![], + annotations: vec![], + }], + }, + docs: vec![], + annotations: vec![], + }, + ServiceEvent { + name: "Three".to_string(), + def: StructDef { + fields: vec![ + StructField { + name: Some("field1".to_string()), + type_decl: Primitive(PrimitiveType::ActorId), + docs: vec![], + annotations: vec![], + }, + StructField { + name: Some("field2".to_string()), + type_decl: Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ], + }, + docs: vec![], + annotations: vec![], + }, + ] + ); + + assert_eq!(service.types.len(), 1); + assert!(matches!( + service.types.get(0), + Some(sails_idl_meta::Type { name, .. }) if name == "EventTwoParams" + )); + } + + // ------------------------------------------------------------------------------------ + // ----------------------------- Functions related tests ------------------------------ + // ------------------------------------------------------------------------------------ + + /// Test error when commands/queries are not variant types + #[test] + fn service_functions_non_variant_error() { + struct NotVariantCommandsService; + impl ServiceMeta for NotVariantCommandsService { + type CommandsMeta = NotVariantCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 90u64.to_le_bytes(); + } + + struct NotVariantQueriesService; + impl ServiceMeta for NotVariantQueriesService { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = NotVariantQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 91u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NotVariantCommands { + pub field: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NotVariantQueries(u32); + + let internal_check = |result: Result>| { + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert!(msg.contains("references a type that is not a variant")); + }; + + internal_check(test_service_units::( + "TestService", + )); + internal_check(test_service_units::( + "TestService", + )); + } + + /// Test error when service variant doesn't have exactly 2 fields + #[test] + fn service_variant_field_count_error() { + struct InvalidCommandsService1; + impl ServiceMeta for InvalidCommandsService1 { + type CommandsMeta = BadCommands1; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 100u64.to_le_bytes(); + } + + struct InvalidCommandsService2; + impl ServiceMeta for InvalidCommandsService2 { + type CommandsMeta = BadCommands2; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 101u64.to_le_bytes(); + } + + struct InvalidQueriesService1; + impl ServiceMeta for InvalidQueriesService1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = BadQueries1; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 102u64.to_le_bytes(); + } + + struct InvalidQueriesService2; + impl ServiceMeta for InvalidQueriesService2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = BadQueries2; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 103u64.to_le_bytes(); + } + + // Commands/queries with wrong number of fields + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands1 { + OneField(u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands2 { + ThreeFields(u32, String, bool), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadQueries1 { + OneField(u32), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadQueries2 { + ThreeFields(u32, String, bool), + } + + let internal_check = |result: Result>, expected_msg: &str| { + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!(msg.as_str(), expected_msg); + }; + + internal_check( + test_service_units::("TestService"), + "command `OneField` has invalid number of fields", + ); + internal_check( + test_service_units::("TestService"), + "query `OneField` has invalid number of fields", + ); + internal_check( + test_service_units::("TestService"), + "command `ThreeFields` has invalid number of fields", + ); + internal_check( + test_service_units::("TestService"), + "query `ThreeFields` has invalid number of fields", + ); + } + + /// Test error when service method params are not composite + #[test] + fn service_params_non_composite_error() { + struct TestServiceMeta; + impl ServiceMeta for TestServiceMeta { + type CommandsMeta = BadCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 110u64.to_le_bytes(); + } + + // Commands where the first field (params) is not composite + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands { + BadCmd(u32, String), + } + + let result = test_service_units::("TestService"); + + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!( + msg.as_str(), + "command `BadCmd` params type is not a composite" + ); + } + + /// Test error when service method params have nameless fields + #[test] + fn service_params_nameless_fields_error() { + struct BadServiceMeta; + impl ServiceMeta for BadServiceMeta { + type CommandsMeta = BadCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 111u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum BadCommands { + BadCmd(NamelessParams, String), + } + + // Tuple struct with nameless fields for params + #[derive(TypeInfo)] + #[allow(unused)] + struct NamelessParams(u32, String); + + let result = test_service_units::("TestService"); + + assert!(result.is_err()); + let Err(Error::FuncMetaIsInvalid(msg)) = result else { + panic!("Expected FuncMetaIsInvalid error, got {result:?}"); + }; + assert_eq!(msg.as_str(), "command `BadCmd` param is missing a name"); + } + + #[test] + fn service_fns_result_ty() { + struct TestServiceMeta; + impl ServiceMeta for TestServiceMeta { + type CommandsMeta = TestCommands; + type QueriesMeta = TestQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 120u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum TestCommands { + Unit(utils::SimpleFunctionParams, ()), + NonUnit(utils::SimpleFunctionParams, String), + WithUnit(utils::SimpleFunctionParams, Result<(), u32>), + Result(utils::SimpleFunctionParams, Result), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum TestQueries { + Unit(utils::SimpleFunctionParams, ()), + NonUnit(utils::SimpleFunctionParams, u32), + WithUnit(utils::SimpleFunctionParams, Result<(), u32>), + Result(utils::SimpleFunctionParams, Result), + } + + let services = + test_service_units::("TestService").expect("ServiceBuilder error"); + assert_eq!(services.len(), 1); + let service = &services[0]; + + let get = |name: &str, kind: FunctionKind| -> &ServiceFunc { + service + .funcs + .iter() + .find(|f| f.name == name && f.kind == kind) + .unwrap_or_else(|| panic!("missing {kind:?} {name}")) + }; + + assert_eq!( + get("Unit", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!(get("Unit", FunctionKind::Command).throws, None); + + assert_eq!( + get("NonUnit", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::String) + ); + assert_eq!(get("NonUnit", FunctionKind::Command).throws, None); + + assert_eq!( + get("WithUnit", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!( + get("WithUnit", FunctionKind::Command).throws, + Some(TypeDecl::Primitive(PrimitiveType::U32)) + ); + + assert_eq!( + get("Result", FunctionKind::Command).output, + TypeDecl::Primitive(PrimitiveType::U32) + ); + assert_eq!( + get("Result", FunctionKind::Command).throws, + Some(TypeDecl::Primitive(PrimitiveType::String)) + ); + + assert_eq!( + get("Unit", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!(get("Unit", FunctionKind::Query).throws, None); + + assert_eq!( + get("NonUnit", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::U32) + ); + assert_eq!(get("NonUnit", FunctionKind::Query).throws, None); + + assert_eq!( + get("WithUnit", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::Void) + ); + assert_eq!( + get("WithUnit", FunctionKind::Query).throws, + Some(TypeDecl::Primitive(PrimitiveType::U32)) + ); + + assert_eq!( + get("Result", FunctionKind::Query).output, + TypeDecl::Primitive(PrimitiveType::U32) + ); + assert_eq!( + get("Result", FunctionKind::Query).throws, + Some(TypeDecl::Primitive(PrimitiveType::String)) + ); + } + + #[test] + fn service_function_variations_positive_test() { + struct ServiceWithOneCommand; + impl ServiceMeta for ServiceWithOneCommand { + type CommandsMeta = OneFunction; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 130u64.to_le_bytes(); + } + + struct ServiceWithOneQuery; + impl ServiceMeta for ServiceWithOneQuery { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = OneFunction; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 131u64.to_le_bytes(); + } + + struct ServiceWithNoFunctions; + impl ServiceMeta for ServiceWithNoFunctions { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 132u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum OneFunction { + Fn1(utils::SimpleFunctionParams, String), + } + + let internal_check = |service: &ServiceUnit, + expected_commands_count: usize, + expected_queries_count: usize| { + let commands_count = service + .funcs + .iter() + .filter(|f| f.kind == FunctionKind::Command) + .count(); + let queries_count = service + .funcs + .iter() + .filter(|f| f.kind == FunctionKind::Query) + .count(); + + assert_eq!(commands_count, expected_commands_count); + assert_eq!(queries_count, expected_queries_count); + + if expected_commands_count > 0 { + assert!( + service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Command && f.name == "Fn1") + ); + } + if expected_queries_count > 0 { + assert!( + service + .funcs + .iter() + .any(|f| f.kind == FunctionKind::Query && f.name == "Fn1") + ); + } + }; + + let svc = test_service_units::("TestService").unwrap(); + internal_check(&svc[0], 1, 0); + let svc = test_service_units::("TestService").unwrap(); + internal_check(&svc[0], 0, 1); + let svc = test_service_units::("TestService").unwrap(); + internal_check(&svc[0], 0, 0); + + struct Service; + impl ServiceMeta for Service { + type CommandsMeta = ServiceCommands; + type QueriesMeta = ServiceQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 133u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ServiceCommands { + NoArgs(NoArgs, String), + OneArg(OneArg, u32), + MultiArgs(MultiArgs, bool), + NoResult(OneArg, ()), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum ServiceQueries { + NoArgs(NoArgs, String), + OneArg(OneArg, u32), + MultiArgs(MultiArgs, bool), + NoResult(OneArg, ()), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NoArgs; + + #[derive(TypeInfo)] + #[allow(unused)] + struct OneArg { + pub arg1: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct MultiArgs { + pub arg1: u32, + pub arg2: String, + pub arg3: bool, + } + + let service = test_service_units::("TestService").unwrap(); + let service = &service[0]; + + let get = |name: &str, kind: FunctionKind| -> &ServiceFunc { + service + .funcs + .iter() + .find(|f| f.name == name && f.kind == kind) + .unwrap_or_else(|| panic!("missing {kind:?} {name}")) + }; + + for kind in [FunctionKind::Command, FunctionKind::Query] { + assert_eq!(get("NoArgs", kind).params.len(), 0); + + let one_arg = get("OneArg", kind); + assert_eq!(one_arg.params.len(), 1); + assert_eq!(one_arg.params[0].name, "arg1"); + + let multi_args = get("MultiArgs", kind); + assert_eq!(multi_args.params.len(), 3); + assert_eq!(multi_args.params[0].name, "arg1"); + assert_eq!(multi_args.params[1].name, "arg2"); + assert_eq!(multi_args.params[2].name, "arg3"); + + let no_result = get("NoResult", kind); + assert_eq!(no_result.output, TypeDecl::Primitive(PrimitiveType::Void)); + assert_eq!(no_result.throws, None); + } + } + + // ------------------------------------------------------------------------------------ + // --------------------------- Types section related tests ---------------------------- + // ------------------------------------------------------------------------------------ + + /// Test that services with only primitive/builtin types have empty types sections + #[test] + fn service_non_user_defined_types_excluded() { + struct Service1; + impl ServiceMeta for Service1 { + type CommandsMeta = CommandsWithNonUserDefinedArgs; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 140u64.to_le_bytes(); + } + + struct Service2; + impl ServiceMeta for Service2 { + type CommandsMeta = CommandWithUserDefinedArgs; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 141u64.to_le_bytes(); + } + + struct Service3; + impl ServiceMeta for Service3 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = CommandsWithNonUserDefinedArgs; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 142u64.to_le_bytes(); + } + + struct Service4; + impl ServiceMeta for Service4 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = CommandWithUserDefinedArgs; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 143u64.to_le_bytes(); + } + + struct Service5; + impl ServiceMeta for Service5 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = EventsWithNonUserDefinedArgs; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 144u64.to_le_bytes(); + } + + struct Service6; + impl ServiceMeta for Service6 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = EventsWithUserDefinedArgs; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 145u64.to_le_bytes(); + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum CommandWithUserDefinedArgs { + Cmd1(UserDefinedParams, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct UserDefinedParams { + pub arg1: NonUserDefinedArgs, + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum CommandsWithNonUserDefinedArgs { + Cmd1(NonUserDefinedArgs, String), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum EventsWithNonUserDefinedArgs { + Event1 { + number: u32, + flag: bool, + text: String, + actor: ActorId, + option_val: Option, + result_val: Result, + map: BTreeMap, + code: CodeId, + message: MessageId, + h160: H160, + h256: H256, + u256: U256, + // non_zero_u8: NonZeroU8, + // non_zero_u16: NonZeroU16, + // non_zero_u32: NonZeroU32, + // non_zero_u64: NonZeroU64, + // non_zero_u128: NonZeroU128, + // non_zero_u256: NonZeroU256, + }, + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum EventsWithUserDefinedArgs { + Event1(NonUserDefinedArgs), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NonUserDefinedArgs { + pub number: u32, + pub flag: bool, + pub text: String, + pub actor: ActorId, + pub option_val: Option, + pub result_val: Result, + pub map: BTreeMap, + pub code: CodeId, + pub message: MessageId, + pub h160: H160, + pub h256: H256, + pub u256: U256, + // pub non_zero_u8: NonZeroU8, + // pub non_zero_u16: NonZeroU16, + // pub non_zero_u32: NonZeroU32, + // pub non_zero_u64: NonZeroU64, + // pub non_zero_u128: NonZeroU128, + // pub non_zero_u256: NonZeroU256, + } + + let check = |service: &ServiceUnit, expected_type_count: usize| { + assert_eq!(service.types.len(), expected_type_count); + if expected_type_count == 1 { + assert!(matches!( + service.types.get(0), + Some(sails_idl_meta::Type { name, .. }) if name == "NonUserDefinedArgs" + )); + } + }; + + check(&test_service_units::("Service1").unwrap()[0], 0); + check(&test_service_units::("Service2").unwrap()[0], 1); + + check(&test_service_units::("Service3").unwrap()[0], 0); + check(&test_service_units::("Service4").unwrap()[0], 1); + + check(&test_service_units::("Service5").unwrap()[0], 0); + check(&test_service_units::("Service6").unwrap()[0], 1); + } + + #[test] + fn ctor_non_user_defined_types_excluded() { + #[derive(TypeInfo)] + #[allow(unused)] + enum CtorsWithNonUserDefinedArgs { + Ctor1(NonUserDefinedCtorArgs), + } + + #[derive(TypeInfo)] + #[allow(unused)] + enum CtorsWithUserDefinedArgs { + Ctor2(UserDefinedCtorArgs), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct NonUserDefinedCtorArgs { + pub number: u32, + pub flag: bool, + pub text: String, + pub actor: ActorId, + pub option_val: Option, + pub result_val: Result, + pub map: BTreeMap, + pub code: CodeId, + pub message: MessageId, + pub h160: H160, + pub h256: H256, + pub u256: U256, + // pub non_zero_u8: NonZeroU8, + // pub non_zero_u16: NonZeroU16, + // pub non_zero_u32: NonZeroU32, + // pub non_zero_u64: NonZeroU64, + // pub non_zero_u128: NonZeroU128, + // pub non_zero_u256: NonZeroU256, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct UserDefinedCtorArgs { + pub custom: CustomType, + pub number: u32, + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct CustomType { + pub value: String, + } + + let meta1 = + test_program_unit::().expect("ProgramBuilder error"); + assert!(meta1.types.is_empty()); + + let meta2 = test_program_unit::().expect("ProgramBuilder error"); + assert_eq!(meta2.types.len(), 1); + assert!(matches!( + meta2.types.get(0), + Some(sails_idl_meta::Type { name, .. }) if name == "CustomType" + )); + } + + // -------------------------------------------------------------------------------- + // ------------------------------ Miscellaneous tests ----------------------------- + // -------------------------------------------------------------------------------- + + #[test] + fn shared_and_same_name_types_across_services() { + struct Service1Meta; + impl ServiceMeta for Service1Meta { + type CommandsMeta = Service1Commands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 150u64.to_le_bytes(); + } + + struct Service2Meta; + impl ServiceMeta for Service2Meta { + type CommandsMeta = Service2Commands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 151u64.to_le_bytes(); + } + + // First service using both shared types + #[derive(TypeInfo)] + #[allow(unused)] + enum Service1Commands { + Cmd1(ServiceCommandParams, String), + Cmd2(ServiceCommandParams, SharedCustomType), + } + + // Second service using both shared types + #[derive(TypeInfo)] + #[allow(unused)] + enum Service2Commands { + Cmd3(ServiceCommandParams, String), + Cmd4(ServiceCommandParams, SharedCustomType), + } + + #[derive(TypeInfo)] + #[allow(unused)] + struct ServiceCommandParams { + param1: SimpleFunctionParams, + param2: utils::SimpleFunctionParams, + } + + // Define SimpleFunctionParams in local scope + #[derive(TypeInfo)] + #[allow(unused)] + struct SimpleFunctionParams { + f1: SharedCustomType, + } + + // Define a custom type to be reused across services + #[derive(TypeInfo)] + #[allow(unused)] + struct SharedCustomType; + + let service_1 = &test_service_units::("Service1").unwrap()[0]; + let service_2 = &test_service_units::("Service2").unwrap()[0]; + + assert_eq!(service_1.types.len(), 3); + assert_eq!(service_2.types.len(), 3); + + let type_names = |svc: &ServiceUnit| -> BTreeSet { + svc.types.iter().map(|t| t.name.clone()).collect() + }; + assert_eq!(type_names(service_1), type_names(service_2)); + assert!(type_names(service_1).contains("SharedCustomType")); + + let simple_params = service_1 + .types + .iter() + .filter(|t| t.name.contains("SimpleFunctionParams")) + .collect::>(); + assert_eq!(simple_params.len(), 2); + + let mut has_u32_field = false; + let mut has_shared_custom_type_field = false; + for ty in simple_params { + let sails_idl_meta::TypeDef::Struct(def) = &ty.def else { + continue; + }; + if def + .fields + .iter() + .any(|f| f.type_decl == TypeDecl::Primitive(PrimitiveType::U32)) + { + has_u32_field = true; + } + if def.fields.iter().any(|f| { + matches!(&f.type_decl, TypeDecl::Named { name, .. } if name == "SharedCustomType") + }) { + has_shared_custom_type_field = true; + } + } + + assert!(has_u32_field); + assert!(has_shared_custom_type_field); + } + + #[test] + #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + fn no_repeated_services() { + struct Service1; + impl ServiceMeta for Service1 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 160u64.to_le_bytes(); + } + + struct Service2; + impl ServiceMeta for Service2 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 161u64.to_le_bytes(); + } + + struct Service3; + impl ServiceMeta for Service3 { + type CommandsMeta = utils::NoCommands; + type QueriesMeta = utils::NoQueries; + type EventsMeta = utils::NoEvents; + const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + const ASYNC: bool = false; + const INTERFACE_ID: [u8; 8] = 162u64.to_le_bytes(); + } + + struct TestProgram; + impl ProgramMeta for TestProgram { + type ConstructorsMeta = utils::SimpleCtors; + const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + ("Service11", AnyServiceMeta::new::), + ("Service12", AnyServiceMeta::new::), + ("Service13", AnyServiceMeta::new::), + ("Service21", AnyServiceMeta::new::), + ("Service22", AnyServiceMeta::new::), + ("Service31", AnyServiceMeta::new::), + ]; + const ASYNC: bool = false; + } + + let doc = build_program_ast::(None).unwrap(); + assert_eq!(doc.services.len(), 3); + } +} diff --git a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap index b7dd8e876..a6deaf424 100644 --- a/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap +++ b/rs/idl-gen/tests/snapshots/generator__program_idl_works_with_multiple_services.snap @@ -1,6 +1,6 @@ --- source: rs/idl-gen/tests/generator.rs -expression: generated_idl +expression: idl --- !@sails: 0.9.2 @@ -175,6 +175,6 @@ service SomeService { program TestProgramWithMultipleServicesMeta { services { Service, - SomeService, + SomeService: Service, } } diff --git a/rs/idl-meta/src/ast.rs b/rs/idl-meta/src/ast.rs index 85f695d8f..ffb17c6bc 100644 --- a/rs/idl-meta/src/ast.rs +++ b/rs/idl-meta/src/ast.rs @@ -59,6 +59,7 @@ pub struct ProgramUnit { pub struct ServiceExpo { pub name: String, pub route: Option, + // TODO: interface_id: [u8; 8], pub docs: Vec, pub annotations: Vec<(String, Option)>, } diff --git a/rs/idl-meta/src/lib.rs b/rs/idl-meta/src/lib.rs index ea5e59417..86d8600b8 100644 --- a/rs/idl-meta/src/lib.rs +++ b/rs/idl-meta/src/lib.rs @@ -41,6 +41,7 @@ pub struct AnyServiceMeta { queries: MetaType, events: MetaType, base_services: Vec<(&'static str, AnyServiceMeta)>, + interface_id: [u8; 8], } impl AnyServiceMeta { @@ -50,6 +51,7 @@ impl AnyServiceMeta { queries: S::queries(), events: S::events(), base_services: S::base_services().collect(), + interface_id: S::INTERFACE_ID, } } @@ -70,6 +72,10 @@ impl AnyServiceMeta { .iter() .map(|&(name, ref meta)| (name, meta)) } + + pub fn interface_id(&self) -> &[u8; 8] { + &self.interface_id + } } pub trait ProgramMeta { From 5613c4f68b60bc3815c90a6c28fe881aa5b71454 Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 17 Dec 2025 15:39:56 +0100 Subject: [PATCH 14/16] wip: type resolver tests --- rs/idl-gen/src/type_resolver.rs | 1362 ++++++++++++++++++++++++++++++- 1 file changed, 1358 insertions(+), 4 deletions(-) diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index ac1a8e628..6dd70bd34 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -46,6 +46,20 @@ impl UserDefinedEntry { _ => unreachable!(), } } + + #[cfg(test)] + fn meta_fields(&self) -> Vec { + match &self.meta_type.def { + sails_idl_meta::TypeDef::Struct(StructDef { fields }) => fields.clone(), + sails_idl_meta::TypeDef::Enum(EnumDef { variants }) => { + let mut fields = Vec::new(); + variants.iter().for_each(|v| { + fields.extend(v.def.fields.iter().cloned()); + }); + fields + } + } + } } impl<'a> TypeResolver<'a> { @@ -78,6 +92,11 @@ impl<'a> TypeResolver<'a> { self.map.get(&key) } + #[cfg(test)] + pub fn get_user_defined(&self, name: &str) -> Option<&UserDefinedEntry> { + self.user_defined.get(name) + } + fn build_type_decl_map(&mut self, exclude: BTreeSet) -> Result<()> { let filtered: Vec<_> = self .registry @@ -416,6 +435,7 @@ mod tests { use super::*; use core::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128}; use gprimitives::NonZeroU256; + use sails_idl_meta::TypeDef; use scale_info::{MetaType, Registry, TypeInfo}; #[allow(dead_code)] @@ -616,12 +636,10 @@ mod tests { fn type_resolver_result_type() { let mut registry = Registry::new(); let u32_result_id = registry - .register_type(&MetaType::new::>()) + .register_type(&MetaType::new::>()) .id; let as_generic_param_id = registry - .register_type(&MetaType::new::< - GenericStruct>, - >()) + .register_type(&MetaType::new::>>()) .id; let portable_registry = PortableRegistry::from(registry); let resolver = TypeResolver::from_registry(&portable_registry); @@ -773,6 +791,24 @@ mod tests { type_name_resolution_works!(CodeId); } + #[test] + fn h160_type_name_resolution_works() { + use gprimitives::H160; + type_name_resolution_works!(H160); + } + + #[test] + fn h256_type_name_resolution_works() { + use gprimitives::H256; + type_name_resolution_works!(H256); + } + + #[test] + fn u256_type_name_resolution_works() { + use gprimitives::U256; + type_name_resolution_works!(U256); + } + #[test] fn type_name_minification_works_for_types_with_the_same_mod_depth() { let mut registry = Registry::new(); @@ -838,4 +874,1322 @@ mod tests { assert_eq!(n256_name, "GenericConstStructN256O832"); assert_eq!(n32u256_name, "GenericConstStructN32O8"); } + + #[test] + fn simple_cases_one_generic() { + // Define helper types for the test + #[allow(dead_code)] + #[derive(TypeInfo)] + struct SimpleOneGenericStruct { + // Category 1: Simple generic usage + concrete: u32, + genericless_unit: GenericlessUnitStruct, + genericless_tuple: GenericlessTupleStruct, + genericless_named: GenericlessNamedStruct, + genericless_enum: GenericlessEnum, + genericless_variantless_enum: GenericlessVariantlessEnum, + generic_value: T, + tuple_generic: (String, T, T, u32), + option_generic: Option, + result_generic: Result, + btreemap_generic: BTreeMap, + struct_generic: GenericStruct, + enum_generic: SimpleOneGenericEnum, + + // Category 2: Two-level nested generics + option_of_option: Option>, + result_of_option: Result, String>, + btreemap_nested: BTreeMap, GenericStruct>, + struct_of_option: GenericStruct>, + enum_of_result: SimpleOneGenericEnum>, + + // Category 3: Triple-nested generics + option_triple: Option>>, + result_triple: Result>, String>, + btreemap_triple: BTreeMap>, Result>, + struct_triple: GenericStruct>>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum SimpleOneGenericEnum { + // Category 1: Simple generic usage + NoFields, + GenericValue(T), + TupleGeneric(String, T, T, u32), + OptionGeneric(Option), + ResultGeneric(Result), + BTreeMapGeneric { + map: BTreeMap, + }, + StructGeneric { + inner: GenericStruct, + }, + NestedEnum(NestedGenericEnum), + + // Category 2: Two-level nested generics + OptionOfOption(Option>), + ResultOfOption { + res: Result, String>, + }, + DoubleNested { + btree_map_nested: BTreeMap, GenericStruct>, + struct_nested: GenericStruct>, + }, + + // Category 3: Triple-nested generics + TrippleNested { + option_triple: Option>>, + result_triple: Result>, String>, + }, + OptionTriple(Option>>), + ResultTriple { + res: Result>, String>, + }, + NoFields2, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericlessUnitStruct; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericlessTupleStruct(u32, String); + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct GenericlessNamedStruct { + a: u32, + b: String, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericlessEnum { + Unit, + Tuple(u32, String), + Named { a: u32, b: String }, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum GenericlessVariantlessEnum {} + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum NestedGenericEnum { + First(T), + Second(Vec), + } + + let mut registry = Registry::new(); + + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + + let genericless_unit_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_tuple_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_named_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_enum_id = registry + .register_type(&MetaType::new::()) + .id; + let genericless_variantless_enum_id = registry + .register_type(&MetaType::new::()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check main types + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "SimpleOneGenericStruct" + ); + let struct_generic = resolver + .get_user_defined("SimpleOneGenericStruct") + .expect("struct generic must exist"); + + assert_eq!( + resolver.get(enum_id).unwrap().to_string(), + "SimpleOneGenericEnum" + ); + let enum_generic = resolver + .get_user_defined("SimpleOneGenericEnum") + .expect("enum generic must exist"); + + // For structs: check that expected generic field strings are present + let s_fields: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + + let expect_struct_fields_type_names = vec![ + "u32", + "GenericlessUnitStruct", + "GenericlessTupleStruct", + "GenericlessNamedStruct", + "GenericlessEnum", + "GenericlessVariantlessEnum", + "T", + "(String, T, T, u32)", + "Option", + "Result", + "[(String, T)]", + "GenericStruct", + "SimpleOneGenericEnum", + "Option>", + "Result, String>", + "[(Option, GenericStruct)]", + "GenericStruct>", + "SimpleOneGenericEnum>", + "Option>>", + "Result>, String>", + "[(Option>, Result)]", + "GenericStruct>>", + ]; + + for expected in expect_struct_fields_type_names { + assert!( + s_fields.contains(&expected.to_string()), + "struct missing generic field {}, All fields: {:#?}", + expected, + s_fields + ); + } + // For enums: check the collected `fields` contains expected signatures and variant names + let e_fields: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + + // First let's check no fields variants + let TypeDef::Enum(EnumDef { variants }) = &enum_generic.meta_type.def else { + panic!("Expected enum generic name"); + }; + + let no_fields_variant = &variants[0]; + let no_fields2_variant = &variants[variants.len() - 1]; + + assert_eq!(no_fields_variant.name, "NoFields"); + assert_eq!(no_fields2_variant.name, "NoFields2"); + assert!(no_fields_variant.def.fields.is_empty()); + assert!(no_fields2_variant.def.fields.is_empty()); + + // expected generic strings for enum fields and nested types: + let expect_enum_field_type_names = vec![ + "T", + "String", + "T", + "T", + "u32", + "Option", + "Result", + "[(String, T)]", + "GenericStruct", + "NestedGenericEnum", + "Option>", + "Result, String>", + "[(Option, GenericStruct)]", + "GenericStruct>", + "Option>>", + "Result>, String>", + ]; + + for expected in expect_enum_field_type_names { + assert!( + e_fields.contains(&expected.to_string()), + "enum missing generic field {}. All enum fields/entries: {:#?}", + expected, + e_fields + ); + } + + // Also verify concrete_names for some representative fields to keep parity with original test spirit + // Retrieve struct type to check underlying field concrete ids + let struct_type = portable_registry + .types + .iter() + .find(|t| t.id == struct_id) + .unwrap(); + + if let scale_info::TypeDef::Composite(composite) = &struct_type.ty.type_def { + let generic_value = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string() == "generic_value") + }) + .unwrap(); + assert_eq!( + resolver.get(generic_value.ty.id).unwrap().to_string(), + "u32" + ); + + let tuple_generic = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string() == "tuple_generic") + }) + .unwrap(); + assert_eq!( + resolver.get(tuple_generic.ty.id).unwrap().to_string(), + "(String, u32, u32, u32)" + ); + + let option_generic = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string() == "option_generic") + }) + .unwrap(); + assert_eq!( + resolver.get(option_generic.ty.id).unwrap().to_string(), + "Option" + ); + + let btreemap_generic = composite + .fields + .iter() + .find(|f| { + f.name + .as_ref() + .is_some_and(|s| s.to_string() == "btreemap_generic") + }) + .unwrap(); + assert_eq!( + resolver.get(btreemap_generic.ty.id).unwrap().to_string(), + "[(String, u32)]" + ); + } else { + panic!("Expected composite type"); + } + + let genericless_unit = resolver.get(genericless_unit_id).unwrap(); + assert_eq!(genericless_unit.to_string(), "GenericlessUnitStruct"); + let genericless_unit_defined = resolver.get_user_defined("GenericlessUnitStruct").unwrap(); + assert!(genericless_unit_defined.meta_fields().is_empty()); + + let genericless_tuple = resolver.get(genericless_tuple_id).unwrap(); + assert_eq!(genericless_tuple.to_string(), "GenericlessTupleStruct"); + let genericless_tuple_def = resolver.get_user_defined("GenericlessTupleStruct").unwrap(); + let fields = genericless_tuple_def.meta_fields(); + let expected_fields_value = vec![ + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ]; + assert_eq!(fields, expected_fields_value); + + let genericless_named = resolver.get(genericless_named_id).unwrap(); + assert_eq!(genericless_named.to_string(), "GenericlessNamedStruct"); + let genericless_named_def = resolver.get_user_defined("GenericlessNamedStruct").unwrap(); + let fields = genericless_named_def.meta_fields(); + let expected_fields_value = vec![ + StructField { + name: Some("a".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: Some("b".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ]; + assert_eq!(fields, expected_fields_value); + + let genericless_enum = resolver.get(genericless_enum_id).unwrap(); + assert_eq!(genericless_enum.to_string(), "GenericlessEnum"); + let genericless_enum_def = resolver.get_user_defined("GenericlessEnum").unwrap(); + let TypeDef::Enum(EnumDef { variants }) = &genericless_enum_def.meta_type.def else { + panic!("Expected enum"); + }; + + let expected_variants = vec![ + EnumVariant { + name: "Unit".to_string(), + def: StructDef { fields: vec![] }, + docs: vec![], + annotations: vec![], + }, + EnumVariant { + name: "Tuple".to_string(), + def: StructDef { + fields: vec![ + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: None, + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ], + }, + docs: vec![], + annotations: vec![], + }, + EnumVariant { + name: "Named".to_string(), + def: StructDef { + fields: vec![ + StructField { + name: Some("a".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::U32), + docs: vec![], + annotations: vec![], + }, + StructField { + name: Some("b".to_string()), + type_decl: TypeDecl::Primitive(PrimitiveType::String), + docs: vec![], + annotations: vec![], + }, + ], + }, + docs: vec![], + annotations: vec![], + }, + ]; + assert_eq!(variants, &expected_variants); + + let genericless_variantless_enum = resolver.get(genericless_variantless_enum_id).unwrap(); + assert_eq!( + genericless_variantless_enum.to_string(), + "GenericlessVariantlessEnum" + ); + let genericless_variantless_enum_def = resolver + .get_user_defined("GenericlessVariantlessEnum") + .unwrap(); + let TypeDef::Enum(EnumDef { variants }) = &genericless_variantless_enum_def.meta_type.def + else { + panic!("Expected enum"); + }; + assert!(variants.is_empty()); + } + + #[test] + fn complex_cases_one_generic() { + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ComplexOneGenericStruct { + array_of_generic: [T; 10], + tuple_complex: (T, Vec, [T; 5]), + array_of_tuple: [(T, T); 3], + vec_of_array: Vec<[T; 8]>, + + array_of_option: [Option; 5], + tuple_of_result: (Result, Option), + vec_of_struct: Vec>, + array_of_btreemap: [BTreeMap; 2], + + array_of_vec_of_option: [Vec>; 4], + tuple_triple: (Option>, Result<[T; 3], String>), + vec_of_struct_of_option: Vec>>, + array_complex_triple: [BTreeMap, Result>; 2], + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + #[allow(clippy::type_complexity)] + enum ComplexOneGenericEnum { + ArrayOfGeneric([T; 10]), + TupleComplex(T, Vec, [T; 5]), + ArrayOfTuple([(T, T); 3]), + VecOfArray { + vec: Vec<[T; 8]>, + }, + + ArrayOfOption([Option; 5]), + TupleOfResult { + tuple: (Result, Option), + }, + VecOfStruct(Vec>), + ArrayOfBTreeMap { + array: [BTreeMap; 2], + }, + + ArrayOfVecOfOption([Vec>>; 4]), + TupleTriple { + field1: Option>>, + field2: Result, String>, + }, + VecOfStructOfOption(Vec>>), + ArrayComplexTriple([BTreeMap, String>, Result>; 2]), + } + + // Register types + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check top level resolved names + let struct_complex = resolver.get(struct_id).unwrap(); + assert_eq!(struct_complex.to_string(), "ComplexOneGenericStruct"); + let struct_generic = resolver + .get_user_defined("ComplexOneGenericStruct") + .unwrap(); + // Validate Struct generics + let struct_field_types: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_struct_field_types = vec![ + "[T; 10]", + "(T, [T], [T; 5])", + "[(T, T); 3]", + "[[T; 8]]", + "[Option; 5]", + "(Result, Option)", + "[GenericStruct]", + "[[(String, T)]; 2]", + "[[Option]; 4]", + "(Option<[T]>, Result<[T; 3], String>)", + "[GenericStruct>]", + "[[(Option, Result)]; 2]", + ]; + + for expected in expect_struct_field_types { + assert!( + struct_field_types.contains(&expected.to_string()), + "Struct missing field type {}.\n All: {:#?}", + expected, + struct_field_types + ); + } + + let enum_complex = resolver.get(enum_id).unwrap(); + assert_eq!(enum_complex.to_string(), "ComplexOneGenericEnum"); + let enum_generic = resolver.get_user_defined("ComplexOneGenericEnum").unwrap(); + + let enum_field_types: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_enum_field_types = vec![ + "[T; 10]", + "T", + "[T]", + "[T; 5]", + "[(T, T); 3]", + "[[T; 8]]", + "[Option; 5]", + "(Result, Option)", + "[GenericStruct]", + "[[(String, T)]; 2]", + "[[Option<[T]>]; 4]", + "Option>", + "Result, String>", + "[GenericStruct>]", + "[[([(Option, String)], Result)]; 2]", + ]; + + for expected in expect_enum_field_types { + assert!( + enum_field_types.contains(&expected.to_string()), + "Enum missing field type {}.\n All: {:#?}", + expected, + enum_field_types + ); + } + } + + #[test] + fn multiple_generics() { + use gprimitives::H256; + + fn find_field_struct<'a>( + composite: &'a TypeDefComposite, + name: &str, + ) -> &'a Field { + composite + .fields + .iter() + .find(|f| f.name.as_ref().is_some_and(|s| s.to_string() == name)) + .unwrap_or_else(|| { + panic!("Field `{name}` not found. Fields: {:#?}", composite.fields) + }) + } + + fn find_variant<'a>( + variants: &'a [Variant], + name: &str, + ) -> &'a Variant { + variants + .iter() + .find(|v| v.name == name) + .unwrap_or_else(|| panic!("Variant `{name}` not found. Variants: {variants:#?}")) + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct MultiGenStruct { + // Category 1: Simple and complex types with single generics + just_t1: T1, + just_t2: T2, + just_t3: T3, + array_t1: [T1; 8], + tuple_t2_t3: (T2, T3), + vec_t3: Vec, + + // Category 2: Mixed generics in complex types + tuple_mixed: (T1, T2, T3), + tuple_repeated: (T1, T1, T2, T2, T3, T3), + array_of_tuple: [(T1, T2); 4], + vec_of_array: Vec<[T3; 5]>, + btreemap_t1_t2: BTreeMap, + struct_of_t3: GenericStruct, + enum_mixed: GenericEnum, + + // Category 3: Two-level nested with multiple generics + option_of_result: Option>, + array_of_option: [Option; 6], + vec_of_tuple: Vec<(T2, T3, T1)>, + tuple_of_result: (Result, Option), + btreemap_nested: BTreeMap, Result>, + struct_of_tuple: GenericStruct<(T2, T3)>, + + // Category 4: Triple-nested complex types with multiple generics + option_triple: Option, T2>>, + array_triple: [BTreeMap>; 3], + vec_of_struct_of_option: Vec>>, + array_of_vec_of_tuple: [Vec<(T1, T2)>; 2], + tuple_complex_triple: (Option>, Result<[T2; 4], T3>), + vec_complex: Vec>>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum MultiGenEnum { + // Category 1: Simple and complex types with single generics + JustT1(T1), + JustT2(T2), + JustT3(T3), + ArrayT1([T1; 8]), + TupleT2T3((T2, T3)), + VecT3 { + vec: Vec, + }, + + // Category 2: Mixed generics in complex types + TupleMixed(T1, T2, T3), + TupleRepeated((T1, T1, T2, T2, T3, T3)), + ArrayOfTuple([(T1, T2); 4]), + VecOfArray { + vec: Vec<[T3; 5]>, + }, + BTreeMapT1T2 { + map: BTreeMap, + }, + StructOfT3(GenericStruct), + EnumMixed { + inner: GenericEnum, + }, + + // Category 3: Two-level nested with multiple generics + OptionOfResult(Option>), + ArrayOfOption([Option; 6]), + VecOfTuple(Vec<(T2, T3, T1)>), + TupleOfResult { + field1: Result, + field2: Option, + }, + BTreeMapNested { + map: BTreeMap, Result>, + }, + StructOfTuple(GenericStruct<(T2, T3)>), + + // Category 4: Triple-nested complex types with multiple generics + OptionTriple(Option, T2>>), + ArrayTriple([BTreeMap>; 3]), + VecOfStructOfOption(Vec>>), + ArrayOfVecOfTuple { + array: [Vec<(T1, T2)>; 2], + }, + TupleComplexTriple { + field1: Option>, + field2: Result<[T2; 4], T3>, + }, + VecComplex(Vec>>), + } + + // Register types and build portable registry + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "MultiGenStruct" + ); + assert_eq!( + resolver.get(enum_id).unwrap().to_string(), + "MultiGenEnum" + ); + + let struct_generic = resolver + .get_user_defined("MultiGenStruct") + .expect("MultiGenStruct generic must exist"); + let struct_field_types: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_struct_field_types = vec![ + "T1", + "T2", + "T3", + "[T1; 8]", + "(T2, T3)", + "[T3]", + "(T1, T2, T3)", + "(T1, T1, T2, T2, T3, T3)", + "[(T1, T2); 4]", + "[[T3; 5]]", + "[(T1, T2)]", + "GenericStruct", + "GenericEnum", + "Option>", + "[Option; 6]", + "[(T2, T3, T1)]", + "(Result, Option)", + "[(Option, Result)]", + "GenericStruct<(T2, T3)>", + "Option>", + "[[(T1, Option)]; 3]", + "[GenericStruct>]", + "[[(T1, T2)]; 2]", + "(Option<[T1]>, Result<[T2; 4], T3>)", + "[GenericStruct>]", + ]; + for expected in expect_struct_field_types { + assert!( + struct_field_types.contains(&expected.to_string()), + "MultiGenStruct missing field type {}.\n All: {:#?}", + expected, + struct_field_types + ); + } + + let enum_generic = resolver + .get_user_defined("MultiGenEnum") + .expect("MultiGenEnum generic must exist"); + let enum_field_types: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_enum_field_types = vec![ + "T1", + "T2", + "T3", + "[T1; 8]", + "(T2, T3)", + "[T3]", + "T1", + "T2", + "T3", + "(T1, T1, T2, T2, T3, T3)", + "[(T1, T2); 4]", + "[[T3; 5]]", + "[(T1, T2)]", + "GenericStruct", + "GenericEnum", + "Option>", + "[Option; 6]", + "[(T2, T3, T1)]", + "Result", + "Option", + "[(Option, Result)]", + "GenericStruct<(T2, T3)>", + "Option>", + "[[(T1, Option)]; 3]", + "[GenericStruct>]", + "[[(T1, T2)]; 2]", + "Option<[T1]>", + "Result<[T2; 4], T3>", + "[GenericStruct>]", + ]; + for expected in expect_enum_field_types { + assert!( + enum_field_types.contains(&expected.to_string()), + "MultiGenEnum missing field type {}.\n All: {:#?}", + expected, + enum_field_types + ); + } + + let struct_type = portable_registry + .types + .iter() + .find(|t| t.id == struct_id) + .unwrap(); + if let scale_info::TypeDef::Composite(composite) = &struct_type.ty.type_def { + let just_t1 = find_field_struct(composite, "just_t1"); + assert_eq!(resolver.get(just_t1.ty.id).unwrap().to_string(), "u32"); + + let tuple_t2_t3 = find_field_struct(composite, "tuple_t2_t3"); + assert_eq!( + resolver.get(tuple_t2_t3.ty.id).unwrap().to_string(), + "(String, H256)" + ); + + let vec_t3 = find_field_struct(composite, "vec_t3"); + assert_eq!(resolver.get(vec_t3.ty.id).unwrap().to_string(), "[H256]"); + + let array_triple = find_field_struct(composite, "array_triple"); + assert_eq!( + resolver.get(array_triple.ty.id).unwrap().to_string(), + "[[(u32, Option)]; 3]" + ); + } else { + panic!("Expected composite type"); + } + + let enum_type = portable_registry + .types + .iter() + .find(|t| t.id == enum_id) + .unwrap(); + if let scale_info::TypeDef::Variant(variant) = &enum_type.ty.type_def { + // check a representative tuple-like variant concrete names + let tuple_t2_t3_variant = find_variant(&variant.variants, "TupleT2T3"); + let f0 = &tuple_t2_t3_variant.fields[0]; + assert_eq!( + resolver.get(f0.ty.id).unwrap().to_string(), + "(String, H256)" + ); + + // check option/result shaped variant + let tuple_of_result_variant = find_variant(&variant.variants, "TupleOfResult"); + let field1 = tuple_of_result_variant + .fields + .iter() + .find(|f| f.name.as_ref().is_some_and(|s| s.to_string() == "field1")) + .unwrap(); + assert_eq!( + resolver.get(field1.ty.id).unwrap().to_string(), + "Result" + ); + } else { + panic!("Expected variant type"); + } + } + + #[test] + fn generic_const_with_generic_types() { + use gprimitives::H256; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ConstGenericStruct { + array: [T; N], + value: T, + vec: Vec, + option: Option, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct TwoConstGenericStruct { + array1: [T1; N], + array2: [T2; M], + tuple: (T1, T2), + nested: GenericStruct, + result: Result, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ConstGenericEnum { + Array([T; N]), + Value(T), + Nested { inner: GenericStruct }, + } + + let mut registry = Registry::new(); + + // Register ConstGenericStruct with different N and T values + let struct_n8_u32_id = registry + .register_type(&MetaType::new::>()) + .id; + let struct_n8_string_id = registry + .register_type(&MetaType::new::>()) + .id; + + let struct_n16_u32_id = registry + .register_type(&MetaType::new::>()) + .id; + + assert_ne!(struct_n8_u32_id, struct_n8_string_id); + assert_ne!(struct_n8_u32_id, struct_n16_u32_id); + + // Register TwoConstGenericStruct + let two_const_id = registry + .register_type(&MetaType::new::>()) + .id; + + // Register ConstGenericEnum + let enum_n8_bool_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check ConstGenericStruct with N=8, T=u32 + let struct_n8_u32_decl = resolver.get(struct_n8_u32_id).unwrap().to_string(); + let struct_n8_string_decl = resolver.get(struct_n8_string_id).unwrap().to_string(); + let struct_n16_u32_decl = resolver.get(struct_n16_u32_id).unwrap().to_string(); + let two_const_decl = resolver.get(two_const_id).unwrap().to_string(); + let enum_n8_bool_decl = resolver.get(enum_n8_bool_id).unwrap().to_string(); + + assert_eq!(struct_n8_u32_decl, "ConstGenericStructN8"); + assert_eq!(struct_n8_string_decl, "ConstGenericStructN8"); + assert_eq!(struct_n16_u32_decl, "ConstGenericStructN16"); + assert_eq!(two_const_decl, "TwoConstGenericStructM8N4"); + assert_eq!(enum_n8_bool_decl, "ConstGenericEnumN8"); + + let TypeDecl::Named { + name: struct_n8_u32_name, + .. + } = resolver.get(struct_n8_u32_id).unwrap() + else { + panic!("Expected named type") + }; + let struct_n8_u32 = resolver.get_user_defined(struct_n8_u32_name).unwrap(); + let field_type_names: Vec<_> = struct_n8_u32 + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec!["[T; 8]", "T", "[T]", "Option"]; + for expected in expected_field_type_names { + assert!( + field_type_names.contains(&expected.to_string()), + "ConstGenericStruct1 missing field type name `{expected}`. All: {field_type_names:#?}", + ); + } + + let TypeDecl::Named { + name: two_const_name, + .. + } = resolver.get(two_const_id).unwrap() + else { + panic!("Expected named type") + }; + let two_const_generic = resolver.get_user_defined(two_const_name).unwrap(); + let field_type_names: Vec<_> = two_const_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec![ + "[T1; 4]", + "[T2; 8]", + "(T1, T2)", + "GenericStruct", + "Result", + ]; + for expected in expected_field_type_names { + assert!( + field_type_names.contains(&expected.to_string()), + "TwoConstGenericStruct missing field type name `{expected}`. All: {field_type_names:#?}", + ); + } + + let TypeDecl::Named { + name: enum_n8_bool_name, + .. + } = resolver.get(enum_n8_bool_id).unwrap() + else { + panic!("Expected named type") + }; + let enum_generic = resolver.get_user_defined(enum_n8_bool_name).unwrap(); + let field_type_names: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec!["[T; 8]", "T", "GenericStruct"]; + for expected in expected_field_type_names { + assert!( + field_type_names.contains(&expected.to_string()), + "ConstGenericEnum missing field type name `{expected}`. All: {field_type_names:#?}", + ); + } + } + + // Types for same_name_different_modules test + #[allow(dead_code)] + mod same_name_test { + use super::*; + + pub mod module_a { + use super::*; + + #[derive(TypeInfo)] + pub struct SameName { + pub value: T, + } + } + + pub mod module_b { + use super::*; + + #[derive(TypeInfo)] + pub struct SameName { + pub value: T, + } + } + + pub mod module_c { + use super::*; + + pub mod nested { + use super::*; + + #[derive(TypeInfo)] + pub struct SameName { + pub value: T, + } + } + } + } + + #[test] + fn same_name_different_mods_generic_names() { + use same_name_test::*; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct TestStruct { + field_a: module_a::SameName, + field_b: module_b::SameName, + field_c: module_c::nested::SameName, + generic_a: GenericStruct>, + generic_b: GenericStruct>, + vec_a: Vec>, + option_b: Option>, + result_mix: Result, module_b::SameName>, + } + + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + // Check main type + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "TestStruct" + ); + let struct_generic = resolver + .get_user_defined("TestStruct") + .expect("TestStruct generic must exist"); + let struct_field_type_names: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expected_field_type_names = vec![ + "SameName", + "ModuleBSameName", + "NestedSameName", + "GenericStruct>", + "GenericStruct>", + "[NestedSameName]", + "Option>", + "Result, ModuleBSameName>", + ]; + + for expected in expected_field_type_names { + assert!( + struct_field_type_names.contains(&expected.to_string()), + "TestStruct missing field type name `{expected}`. All: {struct_field_type_names:#?}", + ); + } + } + + #[test] + fn type_names_concrete_generic_reuses() { + use gprimitives::{CodeId, H256}; + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ReuseTestStruct { + // Same type with different generic instantiations + a1: ReusableGenericStruct, + a1r: ReusableGenericStruct, + + a2: ReusableGenericStruct>, + a2r: ReusableGenericStruct>, + + a3: ReusableGenericStruct<(T1, T2)>, + a3r: ReusableGenericStruct<(u64, H256)>, + + b1: ReusableGenericStruct, + b1r: ReusableGenericStruct, + + // Same enum with different instantiations + e1: ReusableGenericEnum, + e1r: ReusableGenericEnum, + + e2: ReusableGenericEnum, + e2r: ReusableGenericEnum, + + e3: ReusableGenericEnum, + e3r: ReusableGenericEnum<[T1; 8]>, + + // Nested reuses + n1: GenericStruct>, + n2: GenericStruct>, + n3: GenericStruct>, + + // Complex reuses + c1: Vec>, + c2: [ReusableGenericEnum; 5], + c3: Option>, + c4: Result, ReusableGenericEnum>, + c5: BTreeMap>, + c6: BTreeMap, String>, + c7: BTreeMap, ReusableGenericEnum>, + c8: BTreeMap, ReusableGenericEnum>, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ReuseTestEnum { + // Same type with different generic instantiations + A1(ReusableGenericStruct), + A1r(ReusableGenericStruct), + + A2(ReusableGenericStruct>), + A2r(ReusableGenericStruct>), + + A3 { + field: ReusableGenericStruct<(T1, T2)>, + }, + A3r { + field: ReusableGenericStruct<(u64, H256)>, + }, + + B1(ReusableGenericStruct), + B1r(ReusableGenericStruct), + + // Same enum with different instantiations + E1(ReusableGenericEnum), + E1r(ReusableGenericEnum), + + E2(ReusableGenericEnum), + E2r(ReusableGenericEnum), + + E3 { + field: ReusableGenericEnum, + }, + E3r { + field: ReusableGenericEnum<[T1; 8]>, + }, + + // Nested reuses + N1(GenericStruct>), + N2(GenericStruct>), + N3(GenericStruct>), + + // Complex reuses + C1(Vec>), + C2 { + field: [ReusableGenericEnum; 5], + }, + C3(Option>), + C4(Result, ReusableGenericEnum>), + C5 { + field: BTreeMap>, + }, + C6(BTreeMap, String>), + C7(BTreeMap, ReusableGenericEnum>), + C8(BTreeMap, ReusableGenericEnum>), + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + struct ReusableGenericStruct { + data: T, + count: u32, + } + + #[allow(dead_code)] + #[derive(TypeInfo)] + enum ReusableGenericEnum { + Some(T), + None, + } + + let mut registry = Registry::new(); + let struct_id = registry + .register_type(&MetaType::new::>()) + .id; + let enum_id = registry + .register_type(&MetaType::new::>()) + .id; + + let portable_registry = PortableRegistry::from(registry); + let resolver = TypeResolver::from_registry(&portable_registry); + + assert_eq!( + resolver.get(struct_id).unwrap().to_string(), + "ReuseTestStruct" + ); + assert_eq!( + resolver.get(enum_id).unwrap().to_string(), + "ReuseTestEnum" + ); + + let struct_generic = resolver + .get_user_defined("ReuseTestStruct") + .expect("ReuseTestStruct generic must exist"); + let enum_generic = resolver + .get_user_defined("ReuseTestEnum") + .expect("ReuseTestEnum generic must exist"); + + let struct_field_types: Vec<_> = struct_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_struct_field_types = vec![ + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericStruct<[T1]>", + "ReusableGenericStruct<[bool]>", + "ReusableGenericStruct<(T1, T2)>", + "ReusableGenericStruct<(u64, H256)>", + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum<[T1; 8]>", + "GenericStruct>", + "GenericStruct>", + "GenericStruct>", + "[ReusableGenericStruct]", + "[ReusableGenericEnum; 5]", + "Option>", + "Result, ReusableGenericEnum>", + "[(T1, ReusableGenericStruct)]", + "[(ReusableGenericEnum, String)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + ]; + + for e in expect_struct_field_types { + assert!( + struct_field_types.contains(&e.to_string()), + "{} missing expected type signature `{}`. All entries: {:#?}", + "ReuseTestStruct", + e, + struct_field_types + ); + } + + let enum_field_types: Vec<_> = enum_generic + .meta_fields() + .iter() + .map(|f| f.type_decl.to_string()) + .collect(); + let expect_enum_field_types = vec![ + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericStruct<[T1]>", + "ReusableGenericStruct<[bool]>", + "ReusableGenericStruct<(T1, T2)>", + "ReusableGenericStruct<(u64, H256)>", + "ReusableGenericStruct", + "ReusableGenericStruct", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum", + "ReusableGenericEnum<[T1; 8]>", + "GenericStruct>", + "GenericStruct>", + "GenericStruct>", + "[ReusableGenericStruct]", + "[ReusableGenericEnum; 5]", + "Option>", + "Result, ReusableGenericEnum>", + "[(T1, ReusableGenericStruct)]", + "[(ReusableGenericEnum, String)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + "[(ReusableGenericStruct, ReusableGenericEnum)]", + ]; + + for e in expect_enum_field_types { + assert!( + enum_field_types.contains(&e.to_string()), + "{} missing expected type signature `{}`. All entries: {:#?}", + "ReuseTestEnum", + e, + enum_field_types + ); + } + } } From fc16ccec878e9c980f0c31c600f15296f0b58f4e Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 17 Dec 2025 18:28:52 +0100 Subject: [PATCH 15/16] fix: clippy --- rs/idl-gen/src/builder.rs | 8 +++---- rs/idl-gen/src/type_resolver.rs | 38 +++++++++++---------------------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 23d65549a..04308ff8a 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -695,7 +695,7 @@ mod tests { assert_eq!(meta.types.len(), 1); assert!(matches!( - meta.types.get(0), + meta.types.first(), Some(sails_idl_meta::Type { name, .. }) if name == "CtorType" )); } @@ -1390,7 +1390,7 @@ mod tests { assert_eq!(service.types.len(), 1); assert!(matches!( - service.types.get(0), + service.types.first(), Some(sails_idl_meta::Type { name, .. }) if name == "EventTwoParams" )); } @@ -2010,7 +2010,7 @@ mod tests { assert_eq!(service.types.len(), expected_type_count); if expected_type_count == 1 { assert!(matches!( - service.types.get(0), + service.types.first(), Some(sails_idl_meta::Type { name, .. }) if name == "NonUserDefinedArgs" )); } @@ -2083,7 +2083,7 @@ mod tests { let meta2 = test_program_unit::().expect("ProgramBuilder error"); assert_eq!(meta2.types.len(), 1); assert!(matches!( - meta2.types.get(0), + meta2.types.first(), Some(sails_idl_meta::Type { name, .. }) if name == "CustomType" )); } diff --git a/rs/idl-gen/src/type_resolver.rs b/rs/idl-gen/src/type_resolver.rs index 6dd70bd34..f251d8db0 100644 --- a/rs/idl-gen/src/type_resolver.rs +++ b/rs/idl-gen/src/type_resolver.rs @@ -2,7 +2,7 @@ use super::*; use convert_case::{Case, Casing}; use scale_info::{ Field, PortableRegistry, StaticTypeInfo, Type, TypeDef, TypeDefComposite, TypeDefPrimitive, - TypeDefVariant, form::PortableForm, + TypeDefVariant, }; #[derive(Debug, Clone)] @@ -1063,9 +1063,7 @@ mod tests { for expected in expect_struct_fields_type_names { assert!( s_fields.contains(&expected.to_string()), - "struct missing generic field {}, All fields: {:#?}", - expected, - s_fields + "struct missing generic field {expected}, All fields: {s_fields:#?}" ); } // For enums: check the collected `fields` contains expected signatures and variant names @@ -1111,9 +1109,7 @@ mod tests { for expected in expect_enum_field_type_names { assert!( e_fields.contains(&expected.to_string()), - "enum missing generic field {}. All enum fields/entries: {:#?}", - expected, - e_fields + "enum missing generic field {expected}. All enum fields/entries: {e_fields:#?}" ); } @@ -1132,7 +1128,7 @@ mod tests { .find(|f| { f.name .as_ref() - .is_some_and(|s| s.to_string() == "generic_value") + .is_some_and(|s| s.to_string().eq("generic_value")) }) .unwrap(); assert_eq!( @@ -1146,7 +1142,7 @@ mod tests { .find(|f| { f.name .as_ref() - .is_some_and(|s| s.to_string() == "tuple_generic") + .is_some_and(|s| s.to_string().eq("tuple_generic")) }) .unwrap(); assert_eq!( @@ -1160,7 +1156,7 @@ mod tests { .find(|f| { f.name .as_ref() - .is_some_and(|s| s.to_string() == "option_generic") + .is_some_and(|s| s.to_string().eq("option_generic")) }) .unwrap(); assert_eq!( @@ -1174,7 +1170,7 @@ mod tests { .find(|f| { f.name .as_ref() - .is_some_and(|s| s.to_string() == "btreemap_generic") + .is_some_and(|s| s.to_string().eq("btreemap_generic")) }) .unwrap(); assert_eq!( @@ -1396,9 +1392,7 @@ mod tests { for expected in expect_struct_field_types { assert!( struct_field_types.contains(&expected.to_string()), - "Struct missing field type {}.\n All: {:#?}", - expected, - struct_field_types + "Struct missing field type {expected}.\n All: {struct_field_types:#?}" ); } @@ -1432,9 +1426,7 @@ mod tests { for expected in expect_enum_field_types { assert!( enum_field_types.contains(&expected.to_string()), - "Enum missing field type {}.\n All: {:#?}", - expected, - enum_field_types + "Enum missing field type {expected}.\n All: {enum_field_types:#?}" ); } } @@ -1450,7 +1442,7 @@ mod tests { composite .fields .iter() - .find(|f| f.name.as_ref().is_some_and(|s| s.to_string() == name)) + .find(|f| f.name.as_ref().is_some_and(|s| s.to_string().eq(name))) .unwrap_or_else(|| { panic!("Field `{name}` not found. Fields: {:#?}", composite.fields) }) @@ -1616,9 +1608,7 @@ mod tests { for expected in expect_struct_field_types { assert!( struct_field_types.contains(&expected.to_string()), - "MultiGenStruct missing field type {}.\n All: {:#?}", - expected, - struct_field_types + "MultiGenStruct missing field type {expected}.\n All: {struct_field_types:#?}" ); } @@ -1664,9 +1654,7 @@ mod tests { for expected in expect_enum_field_types { assert!( enum_field_types.contains(&expected.to_string()), - "MultiGenEnum missing field type {}.\n All: {:#?}", - expected, - enum_field_types + "MultiGenEnum missing field type {expected}.\n All: {enum_field_types:#?}" ); } @@ -1716,7 +1704,7 @@ mod tests { let field1 = tuple_of_result_variant .fields .iter() - .find(|f| f.name.as_ref().is_some_and(|s| s.to_string() == "field1")) + .find(|f| f.name.as_ref().is_some_and(|s| s.to_string().eq("field1"))) .unwrap(); assert_eq!( resolver.get(field1.ty.id).unwrap().to_string(), From 4cfeabcf5305d0b756c60216ae435846a423fe1e Mon Sep 17 00:00:00 2001 From: vobradovich Date: Wed, 17 Dec 2025 19:37:26 +0100 Subject: [PATCH 16/16] comment ignored tests --- rs/idl-gen/src/builder.rs | 476 +++++++++++++++++++------------------- 1 file changed, 238 insertions(+), 238 deletions(-) diff --git a/rs/idl-gen/src/builder.rs b/rs/idl-gen/src/builder.rs index 04308ff8a..a0332bc0b 100644 --- a/rs/idl-gen/src/builder.rs +++ b/rs/idl-gen/src/builder.rs @@ -623,53 +623,53 @@ mod tests { ); } - #[test] - #[ignore = "TODO"] - fn program_has_same_name_services() { - struct TestService; - impl ServiceMeta for TestService { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::zero(); - } - - struct TestProgram; - impl ProgramMeta for TestProgram { - type ConstructorsMeta = utils::SimpleCtors; - const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("TestService", AnyServiceMeta::new::), - ("TestService", AnyServiceMeta::new::), - ]; - const ASYNC: bool = false; - } - - let meta = ProgramBuilder::new::() - .build("TestProgram".to_string()) - .expect("ProgramBuilder error"); - - assert_eq!(meta.services.len(), 2); - assert_eq!( - meta.services[0], - ServiceExpo { - name: "TestService".to_string(), - route: None, - docs: vec![], - annotations: vec![] - } - ); - assert_eq!( - meta.services[1], - ServiceExpo { - name: "TestService".to_string(), - route: Some("TestService".to_string()), - docs: vec![], - annotations: vec![] - } - ); - } + // #[test] + // #[ignore = "TODO"] + // fn program_has_same_name_services() { + // struct TestService; + // impl ServiceMeta for TestService { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::zero(); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("TestService", AnyServiceMeta::new::), + // ("TestService", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let meta = ProgramBuilder::new::() + // .build("TestProgram".to_string()) + // .expect("ProgramBuilder error"); + + // assert_eq!(meta.services.len(), 2); + // assert_eq!( + // meta.services[0], + // ServiceExpo { + // name: "TestService".to_string(), + // route: None, + // docs: vec![], + // annotations: vec![] + // } + // ); + // assert_eq!( + // meta.services[1], + // ServiceExpo { + // name: "TestService".to_string(), + // route: Some("TestService".to_string()), + // docs: vec![], + // annotations: vec![] + // } + // ); + // } #[test] fn program_section_has_types_section() { @@ -1120,147 +1120,147 @@ mod tests { ); } - #[test] - #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] - fn no_repeated_base_services() { - struct BaseService; - impl ServiceMeta for BaseService { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(50u64); - } - - struct Service1; - impl ServiceMeta for Service1 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("BaseService", AnyServiceMeta::new::)]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(51u64); - } - - struct Service2; - impl ServiceMeta for Service2 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("BaseService", AnyServiceMeta::new::)]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(52u64); - } - - struct TestProgram; - impl ProgramMeta for TestProgram { - type ConstructorsMeta = utils::SimpleCtors; - const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("Service1", AnyServiceMeta::new::), - ("Service2", AnyServiceMeta::new::), - ]; - const ASYNC: bool = false; - } - - let doc = build_program_ast::(None).unwrap(); - assert_eq!(doc.services.len(), 3); - } - - #[test] - #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] - fn no_repeated_base_services_with_renaming() { - struct BaseService; - impl ServiceMeta for BaseService { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(60u64); - } - - struct Service1; - impl ServiceMeta for Service1 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("BaseService", AnyServiceMeta::new::)]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(61u64); - } - - struct Service2; - impl ServiceMeta for Service2 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = - &[("RenamedBaseService", AnyServiceMeta::new::)]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(62u64); - } - - struct TestProgram; - impl ProgramMeta for TestProgram { - type ConstructorsMeta = utils::SimpleCtors; - const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("Service1", AnyServiceMeta::new::), - ("Service2", AnyServiceMeta::new::), - ]; - const ASYNC: bool = false; - } - - let doc = build_program_ast::(None).unwrap(); - assert_eq!(doc.services.len(), 3); - } - - #[test] - #[ignore = "TODO [future]: Must be error when Sails binary protocol is implemented"] - fn no_same_service_in_base_services() { - struct ServiceA; - impl ServiceMeta for ServiceA { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(70u64); - } - - struct ServiceB; - impl ServiceMeta for ServiceB { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("ServiceA", AnyServiceMeta::new::), - ("ServiceA", AnyServiceMeta::new::), - ]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(71u64); - } - - assert!(test_service_units::("ServiceB").is_err()); - - struct ServiceC; - impl ServiceMeta for ServiceC { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("ServiceA", AnyServiceMeta::new::), - ("RenamedServiceA", AnyServiceMeta::new::), - ]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(72u64); - } - - assert!(test_service_units::("ServiceC").is_err()); - } + // #[test] + // #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + // fn no_repeated_base_services() { + // struct BaseService; + // impl ServiceMeta for BaseService { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(50u64); + // } + + // struct Service1; + // impl ServiceMeta for Service1 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("BaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(51u64); + // } + + // struct Service2; + // impl ServiceMeta for Service2 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("BaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(52u64); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("Service1", AnyServiceMeta::new::), + // ("Service2", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let doc = build_program_ast::(None).unwrap(); + // assert_eq!(doc.services.len(), 3); + // } + + // #[test] + // #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + // fn no_repeated_base_services_with_renaming() { + // struct BaseService; + // impl ServiceMeta for BaseService { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(60u64); + // } + + // struct Service1; + // impl ServiceMeta for Service1 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("BaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(61u64); + // } + + // struct Service2; + // impl ServiceMeta for Service2 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = + // &[("RenamedBaseService", AnyServiceMeta::new::)]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(62u64); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("Service1", AnyServiceMeta::new::), + // ("Service2", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let doc = build_program_ast::(None).unwrap(); + // assert_eq!(doc.services.len(), 3); + // } + + // #[test] + // #[ignore = "TODO [future]: Must be error when Sails binary protocol is implemented"] + // fn no_same_service_in_base_services() { + // struct ServiceA; + // impl ServiceMeta for ServiceA { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(70u64); + // } + + // struct ServiceB; + // impl ServiceMeta for ServiceB { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("ServiceA", AnyServiceMeta::new::), + // ("ServiceA", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(71u64); + // } + + // assert!(test_service_units::("ServiceB").is_err()); + + // struct ServiceC; + // impl ServiceMeta for ServiceC { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("ServiceA", AnyServiceMeta::new::), + // ("RenamedServiceA", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(72u64); + // } + + // assert!(test_service_units::("ServiceC").is_err()); + // } // ------------------------------------------------------------------------------------ // ------------------------------ Events related tests -------------------------------- @@ -2192,54 +2192,54 @@ mod tests { assert!(has_shared_custom_type_field); } - #[test] - #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] - fn no_repeated_services() { - struct Service1; - impl ServiceMeta for Service1 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(160u64); - } - - struct Service2; - impl ServiceMeta for Service2 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(161u64); - } - - struct Service3; - impl ServiceMeta for Service3 { - type CommandsMeta = utils::NoCommands; - type QueriesMeta = utils::NoQueries; - type EventsMeta = utils::NoEvents; - const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; - const ASYNC: bool = false; - const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(162u64); - } - - struct TestProgram; - impl ProgramMeta for TestProgram { - type ConstructorsMeta = utils::SimpleCtors; - const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ - ("Service11", AnyServiceMeta::new::), - ("Service12", AnyServiceMeta::new::), - ("Service13", AnyServiceMeta::new::), - ("Service21", AnyServiceMeta::new::), - ("Service22", AnyServiceMeta::new::), - ("Service31", AnyServiceMeta::new::), - ]; - const ASYNC: bool = false; - } - - let doc = build_program_ast::(None).unwrap(); - assert_eq!(doc.services.len(), 3); - } + // #[test] + // #[ignore = "TODO [future]: Must be 3 services when Sails binary protocol is implemented"] + // fn no_repeated_services() { + // struct Service1; + // impl ServiceMeta for Service1 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(160u64); + // } + + // struct Service2; + // impl ServiceMeta for Service2 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(161u64); + // } + + // struct Service3; + // impl ServiceMeta for Service3 { + // type CommandsMeta = utils::NoCommands; + // type QueriesMeta = utils::NoQueries; + // type EventsMeta = utils::NoEvents; + // const BASE_SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[]; + // const ASYNC: bool = false; + // const INTERFACE_ID: InterfaceId = InterfaceId::from_u64(162u64); + // } + + // struct TestProgram; + // impl ProgramMeta for TestProgram { + // type ConstructorsMeta = utils::SimpleCtors; + // const SERVICES: &'static [(&'static str, AnyServiceMetaFn)] = &[ + // ("Service11", AnyServiceMeta::new::), + // ("Service12", AnyServiceMeta::new::), + // ("Service13", AnyServiceMeta::new::), + // ("Service21", AnyServiceMeta::new::), + // ("Service22", AnyServiceMeta::new::), + // ("Service31", AnyServiceMeta::new::), + // ]; + // const ASYNC: bool = false; + // } + + // let doc = build_program_ast::(None).unwrap(); + // assert_eq!(doc.services.len(), 3); + // } }