From af44cfb566bf08ebb70026470693f2dd6905a0b7 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 29 Jul 2025 20:15:55 +0200 Subject: [PATCH 01/11] Revert "refactor: revert #612 (#660)" This reverts commit 43160d77521823ea61544c704b2159f5e457a4b1. --- CHANGELOG.md | 8 ++- rust/bench/bench.rs | 7 +- rust/candid/src/binary_parser.rs | 7 +- rust/candid/src/pretty/candid.rs | 35 ++++++++-- rust/candid/src/ser.rs | 6 +- rust/candid/src/types/internal.rs | 66 +++++++++++++++---- rust/candid/src/types/mod.rs | 2 +- rust/candid/src/types/subtype.rs | 48 +++++++++++--- rust/candid/tests/types.rs | 19 +++++- rust/candid_derive/src/func.rs | 42 ++++++++---- rust/candid_parser/src/bindings/analysis.rs | 14 ++-- rust/candid_parser/src/bindings/javascript.rs | 16 +++-- rust/candid_parser/src/bindings/motoko.rs | 38 ++++++++--- rust/candid_parser/src/bindings/rust.rs | 57 ++++++++++------ rust/candid_parser/src/bindings/typescript.rs | 2 +- rust/candid_parser/src/grammar.lalrpop | 26 +++++--- rust/candid_parser/src/lib.rs | 4 +- rust/candid_parser/src/syntax/mod.rs | 30 ++++++++- rust/candid_parser/src/syntax/pretty.rs | 20 ++++-- rust/candid_parser/src/typing.rs | 21 ++++-- rust/candid_parser/src/utils.rs | 7 +- .../tests/assets/collision_arguments.did | 4 ++ .../tests/assets/collision_arguments2.did | 3 + rust/candid_parser/tests/assets/ok/class.did | 2 +- rust/candid_parser/tests/assets/ok/class.mo | 2 +- rust/candid_parser/tests/assets/ok/class.rs | 2 +- .../tests/assets/ok/collision_arguments.fail | 1 + .../tests/assets/ok/collision_arguments2.fail | 1 + .../candid_parser/tests/assets/ok/example.did | 6 +- rust/candid_parser/tests/assets/ok/example.mo | 6 +- rust/candid_parser/tests/assets/ok/example.rs | 8 +-- .../tests/assets/ok/fieldnat.did | 2 +- .../candid_parser/tests/assets/ok/fieldnat.mo | 2 +- .../candid_parser/tests/assets/ok/fieldnat.rs | 4 +- .../candid_parser/tests/assets/ok/keyword.did | 2 +- rust/candid_parser/tests/assets/ok/keyword.mo | 2 +- rust/candid_parser/tests/assets/ok/keyword.rs | 4 +- .../tests/assets/ok/recursion.did | 2 +- .../tests/assets/ok/recursion.mo | 2 +- .../tests/assets/ok/recursion.rs | 4 +- rust/candid_parser/tests/value.rs | 11 +++- tools/didc/src/main.rs | 7 +- 42 files changed, 404 insertions(+), 148 deletions(-) create mode 100644 rust/candid_parser/tests/assets/collision_arguments.did create mode 100644 rust/candid_parser/tests/assets/collision_arguments2.did create mode 100644 rust/candid_parser/tests/assets/ok/collision_arguments.fail create mode 100644 rust/candid_parser/tests/assets/ok/collision_arguments2.fail diff --git a/CHANGELOG.md b/CHANGELOG.md index f0f169ae6..638120980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,22 @@ ### Candid 0.10.16 +* Breaking changes: + + The `args` field of the `candid::types::internal::Function` struct now is a `Vec` instead of `Vec`, to preserve argument names. + + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. + * Non-breaking changes: + Makes the warning message for the special opt subtyping rule more explicit in the `candid::types::subtype::subtype` and `candid::types::subtype::subtype_with_config` functions. - + Added `pp_label_raw` in `pretty::candid` module. + + Added `pp_named_args`, `pp_named_init_args` and `pp_label_raw` in `pretty::candid` module. ### candid_parser 0.2.1 * Breaking changes: + The `candid_parser::types` module has been renamed to `candid_parser::syntax`. + + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. * Non-breaking changes: + + Supports parsing the arguments' names for `func` and `service` (init args). + Supports collecting line comments as doc comments in the following cases: - above services: ``` diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 9ec60beed..5f6e0272d 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -216,10 +216,15 @@ fn nns() -> BenchResult { let args = candid_parser::parse_idl_args(motion_proposal).unwrap(); let serv = serv.unwrap(); let method = &env.get_method(&serv, "manage_neuron").unwrap(); + let arg_tys = method + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); drop(_p); let bytes = { let _p = bench_scope("1. Encoding"); - args.to_bytes_with_types(&env, &method.args).unwrap() + args.to_bytes_with_types(&env, &arg_tys).unwrap() }; { let _p = bench_scope("2. Decoding"); diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index d0c1ea0ea..ca8cd1636 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -1,4 +1,4 @@ -use crate::types::internal::{Field, Function, Label, Type, TypeInner}; +use crate::types::internal::{ArgType, Field, Function, Label, Type, TypeInner}; use crate::types::{FuncMode, TypeEnv}; use anyhow::{anyhow, Context, Result}; use binread::io::{Read, Seek}; @@ -201,7 +201,10 @@ impl ConsType { let mut args = Vec::new(); let mut rets = Vec::new(); for arg in &f.args { - args.push(arg.to_type(len)?); + args.push(ArgType { + name: None, + typ: arg.to_type(len)?, + }); } for ret in &f.rets { rets.push(ret.to_type(len)?); diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 79ae95d02..0c2733fff 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; use crate::pretty::utils::*; -use crate::types::{Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use crate::types::{ + ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner, +}; use pretty::RcDoc; static KEYWORDS: [&str; 30] = [ @@ -154,7 +156,7 @@ fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc { } pub fn pp_function(func: &Function) -> RcDoc { - let args = pp_args(&func.args); + let args = pp_named_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") @@ -163,7 +165,24 @@ pub fn pp_function(func: &Function) -> RcDoc { .nest(INDENT_SPACE) } +/// Pretty-prints named arguments in the form of `(name1 : type1, name2 : type2)`. +/// +/// To print unnamed arguments, use [`pp_args`] instead. +pub fn pp_named_args(args: &[ArgType]) -> RcDoc { + let args = args.iter().map(|arg| { + if let Some(name) = &arg.name { + pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ)) + } else { + pp_ty(&arg.typ) + } + }); + let doc = concat(args, ","); + enclose("(", doc, ")") +} + /// Pretty-prints arguments in the form of `(type1, type2)`. +/// +/// To print named arguments, use [`pp_named_args`] instead. pub fn pp_args(args: &[Type]) -> RcDoc { let doc = concat(args.iter().map(pp_ty), ","); enclose("(", doc, ")") @@ -214,8 +233,8 @@ fn pp_defs(env: &TypeEnv) -> RcDoc { })) } -fn pp_class<'a>(args: &'a [Type], t: &'a Type, docs: Option<&'a DocComments>) -> RcDoc<'a> { - let doc = pp_args(args).append(" ->").append(RcDoc::space()); +fn pp_class<'a>(args: &'a [ArgType], t: &'a Type, docs: Option<&'a DocComments>) -> RcDoc<'a> { + let doc = pp_named_args(args).append(" ->").append(RcDoc::space()); match t.as_ref() { TypeInner::Service(ref serv) => doc.append(pp_service(serv, docs)), TypeInner::Var(ref s) => doc.append(s), @@ -233,10 +252,18 @@ fn pp_actor<'a>(ty: &'a Type, docs: &'a DocComments) -> RcDoc<'a> { } /// Pretty-prints the initialization arguments for a Candid actor. +/// +/// This function is kept for backward compatibility. +/// It is recommended to use [`pp_named_init_args`] instead, which prints named arguments. pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [Type]) -> RcDoc<'a> { pp_defs(env).append(pp_args(args)) } +/// Pretty-prints the initialization arguments for a Candid actor with named arguments. +pub fn pp_named_init_args<'a>(env: &'a TypeEnv, args: &'a [ArgType]) -> RcDoc<'a> { + pp_defs(env).append(pp_named_args(args)) +} + /// Collects doc comments that can be passed to the [compile_with_docs] function. #[derive(Default, Debug)] pub struct DocComments { diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 2ed78e7a9..53fee18ef 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -335,8 +335,8 @@ impl TypeSerialize { } } TypeInner::Func(ref func) => { - for ty in func.args.iter().chain(func.rets.iter()) { - self.build_type(ty)?; + for ty in &func.args { + self.build_type(&ty.typ)?; } for ty in &func.rets { self.build_type(ty)?; @@ -344,7 +344,7 @@ impl TypeSerialize { sleb128_encode(&mut buf, Opcode::Func as i64)?; leb128_encode(&mut buf, func.args.len() as u64)?; for ty in &func.args { - self.encode(&mut buf, ty)?; + self.encode(&mut buf, &ty.typ)?; } leb128_encode(&mut buf, func.rets.len() as u64)?; for ty in &func.rets { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 0bfd8cc05..4a70cdba0 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -148,7 +148,14 @@ impl TypeContainer { } TypeInner::Func(func) => TypeInner::Func(Function { modes: func.modes.clone(), - args: func.args.iter().map(|arg| self.go(arg)).collect(), + args: func + .args + .iter() + .map(|arg| ArgType { + name: arg.name.clone(), + typ: self.go(&arg.typ), + }) + .collect(), rets: func.rets.iter().map(|arg| self.go(arg)).collect(), }), TypeInner::Service(serv) => TypeInner::Service( @@ -156,9 +163,16 @@ impl TypeContainer { .map(|(id, t)| (id.clone(), self.go(t))) .collect(), ), - TypeInner::Class(inits, ref ty) => { - TypeInner::Class(inits.iter().map(|t| self.go(t)).collect(), self.go(ty)) - } + TypeInner::Class(inits, ref ty) => TypeInner::Class( + inits + .iter() + .map(|t| ArgType { + name: t.name.clone(), + typ: self.go(&t.typ), + }) + .collect(), + self.go(ty), + ), t => t.clone(), } .into() @@ -196,7 +210,7 @@ pub enum TypeInner { Variant(Vec), Func(Function), Service(Vec<(String, Type)>), - Class(Vec, Type), + Class(Vec, Type), Principal, Future, } @@ -278,7 +292,14 @@ impl Type { let func = func.clone(); Func(Function { modes: func.modes, - args: func.args.into_iter().map(|t| t.subst(tau)).collect(), + args: func + .args + .into_iter() + .map(|t| ArgType { + name: t.name, + typ: t.typ.subst(tau), + }) + .collect(), rets: func.rets.into_iter().map(|t| t.subst(tau)).collect(), }) } @@ -287,7 +308,15 @@ impl Type { .map(|(meth, ty)| (meth.clone(), ty.subst(tau))) .collect(), ), - Class(args, ty) => Class(args.iter().map(|t| t.subst(tau)).collect(), ty.subst(tau)), + Class(args, ty) => Class( + args.iter() + .map(|t| ArgType { + name: t.name.clone(), + typ: t.typ.subst(tau), + }) + .collect(), + ty.subst(tau), + ), _ => return self.clone(), } .into() @@ -353,7 +382,12 @@ pub fn text_size(t: &Type, limit: i32) -> Result { let mut cnt = mode + 6; let mut limit = limit - cnt; for t in &func.args { - cnt += text_size(t, limit)?; + if let Some(name) = t.name.as_ref() { + let id_size = name.len() as i32; + cnt += id_size + text_size(&t.typ, limit - id_size - 3)? + 3; + } else { + cnt += text_size(&t.typ, limit)?; + } limit -= cnt; } for t in &func.rets { @@ -510,10 +544,16 @@ pub enum FuncMode { #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] pub struct Function { pub modes: Vec, - pub args: Vec, + pub args: Vec, pub rets: Vec, } +#[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] +pub struct ArgType { + pub name: Option, + pub typ: Type, +} + #[cfg(feature = "printer")] impl fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -540,16 +580,16 @@ impl Function { /// `func!((u8, &str) -> (Nat) query)` expands to `Type(Rc::new(TypeInner::Func(...)))` macro_rules! func { ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Query] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Query] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) composite_query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::CompositeQuery] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::CompositeQuery] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) oneway ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Oneway] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Oneway] })) }; } #[macro_export] diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 49af606db..72b385c90 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -15,7 +15,7 @@ pub mod type_env; pub mod value; pub use self::internal::{ - get_type, Field, FuncMode, Function, Label, SharedLabel, Type, TypeId, TypeInner, + get_type, ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeId, TypeInner, }; pub use type_env::TypeEnv; diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 5c4d9fdd3..11fad4123 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -129,8 +129,18 @@ fn subtype_( if f1.modes != f2.modes { return Err(Error::msg("Function mode mismatch")); } - let args1 = to_tuple(&f1.args); - let args2 = to_tuple(&f2.args); + let f1_args = f1 + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let f2_args = f2 + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let args1 = to_tuple(&f1_args); + let args2 = to_tuple(&f2_args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); subtype_(report, gamma, env, &args2, &args1) @@ -212,8 +222,18 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( if f1.modes != f2.modes { return Err(Error::msg("Function mode mismatch")); } - let args1 = to_tuple(&f1.args); - let args2 = to_tuple(&f2.args); + let f1_args = f1 + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let f2_args = f2 + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let args1 = to_tuple(&f1_args); + let args2 = to_tuple(&f2_args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); equal(gamma, env, &args1, &args2).context("Mismatch in function input type")?; @@ -221,8 +241,16 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( Ok(()) } (Class(init1, ty1), Class(init2, ty2)) => { - let init_1 = to_tuple(init1); - let init_2 = to_tuple(init2); + let init1_typ = init1 + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let init2_typ = init2 + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let init_1 = to_tuple(&init1_typ); + let init_2 = to_tuple(&init2_typ); equal(gamma, env, &init_1, &init_2).context(format!( "Mismatch in init args: {} and {}", pp_args(init1), @@ -277,7 +305,7 @@ fn to_tuple(args: &[Type]) -> Type { .into() } #[cfg(not(feature = "printer"))] -fn pp_args(args: &[crate::types::Type]) -> String { +fn pp_args(args: &[crate::types::ArgType]) -> String { use std::fmt::Write; let mut s = String::new(); write!(&mut s, "(").unwrap(); @@ -288,7 +316,7 @@ fn pp_args(args: &[crate::types::Type]) -> String { s } #[cfg(feature = "printer")] -fn pp_args(args: &[crate::types::Type]) -> String { - use crate::pretty::candid::pp_args; - pp_args(args).pretty(80).to_string() +fn pp_args(args: &[crate::types::ArgType]) -> String { + use crate::pretty::candid::pp_named_args; + pp_named_args(args).pretty(80).to_string() } diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index 688fcf2f9..da0742f08 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -308,7 +308,7 @@ service : (List_2) -> { // on multiple lines "oneway" : (text) -> () oneway; // Doc comment for 🐂 - "🐂" : (text, int32) -> (text, int32) query; + "🐂" : (a : text, b : int32) -> (text, int32) query; }"#; assert_eq!(expected, __export_service()); } @@ -336,6 +336,21 @@ fn test_counter() { } } candid::export_service!(); - let expected = "service : { inc : () -> (); read : () -> (nat64) query; set : (nat64) -> () }"; + let expected = r#"service : { + inc : () -> (); + read : () -> (nat64) query; + set : (value : nat64) -> (); +}"#; + assert_eq!(expected, __export_service()); +} + +#[test] +fn test_init_named_args() { + #[candid_method(init)] + fn init(a: u8) { + let _ = a; + } + candid::export_service!(); + let expected = r#"service : (a : nat8) -> {}"#; assert_eq!(expected, __export_service()); } diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 577424564..1b4487aa6 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -6,9 +6,9 @@ use std::collections::BTreeMap; use std::sync::Mutex; use syn::{Attribute, Error, ItemFn, Meta, Result, ReturnType, Signature, Type}; -type RawArgs = Vec; +type RawArgs = Vec<(Option, String)>; type RawRets = Vec; -type ParsedArgs = Vec; +type ParsedArgs = Vec<(Option, Type)>; type ParsedRets = Vec; struct Method { @@ -43,7 +43,7 @@ pub(crate) fn candid_method(attrs: Vec, fun: ItemFn) -> Result) -> TokenStream { .map(|t| generate_arg(quote! { init_args }, t)) .collect::>(); quote! { - let mut init_args: Vec = Vec::new(); + let mut init_args: Vec = Vec::new(); #(#args)* } }); @@ -138,7 +138,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { quote! { { #doc_storage - let mut args: Vec = Vec::new(); + let mut args: Vec = Vec::new(); #(#args)* let mut rets: Vec = Vec::new(); #(#rets)* @@ -149,7 +149,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { }, ); let service = quote! { - use #candid::types::{CandidType, Function, Type, TypeInner}; + use #candid::types::{CandidType, Function, Type, ArgType, TypeInner}; let mut service = Vec::<(String, Type)>::new(); let mut env = #candid::types::internal::TypeContainer::new(); let mut docs = #candid::pretty::candid::DocComments::empty(); @@ -181,15 +181,22 @@ pub(crate) fn export_service(path: Option) -> TokenStream { } } -fn generate_arg(name: TokenStream, ty: &str) -> TokenStream { - let ty = syn::parse_str::(ty).unwrap(); +fn generate_arg(name: TokenStream, (arg_name, ty): &(Option, String)) -> TokenStream { + let arg_name = arg_name + .as_ref() + .map(|n| quote! { Some(#n.to_string()) }) + .unwrap_or(quote! { None }); + let ty = syn::parse_str::(ty.as_str()).unwrap(); quote! { - #name.push(env.add::<#ty>()); + #name.push(ArgType { name: #arg_name, typ: env.add::<#ty>() }); } } fn generate_ret(name: TokenStream, ty: &str) -> TokenStream { - generate_arg(name, ty) + let ty = syn::parse_str::(ty).unwrap(); + quote! { + #name.push(env.add::<#ty>()); + } } fn get_args(sig: &Signature) -> Result<(ParsedArgs, ParsedRets)> { @@ -201,7 +208,20 @@ fn get_args(sig: &Signature) -> Result<(ParsedArgs, ParsedRets)> { return Err(Error::new_spanned(arg, "only works for borrowed self")); } } - syn::FnArg::Typed(syn::PatType { ty, .. }) => args.push(ty.as_ref().clone()), + syn::FnArg::Typed(syn::PatType { ty, pat, .. }) => { + if let syn::Pat::Ident(syn::PatIdent { ident, .. }) = pat.as_ref() { + let arg_name = ident.to_string(); + if arg_name.starts_with("_") { + // If the argument name starts with _, it usually means it's not used. + // We don't need to include it in the IDL. + args.push((None, ty.as_ref().clone())); + } else { + args.push((Some(arg_name), ty.as_ref().clone())); + } + } else { + args.push((None, ty.as_ref().clone())); + } + } } } let rets = match &sig.output { diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index edd316bc7..bf80a0524 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -56,7 +56,8 @@ pub fn chase_type<'a>( } } Func(f) => { - for ty in f.args.iter().chain(f.rets.iter()) { + let args = f.args.iter().map(|arg| &arg.typ); + for ty in args.clone().chain(f.rets.iter()) { chase_type(seen, res, env, ty)?; } } @@ -67,7 +68,7 @@ pub fn chase_type<'a>( } Class(args, t) => { for arg in args.iter() { - chase_type(seen, res, env, arg)?; + chase_type(seen, res, env, &arg.typ)?; } chase_type(seen, res, env, t)?; } @@ -94,7 +95,7 @@ pub fn chase_def_use<'a>( if let TypeInner::Class(args, _) = actor.as_ref() { for (i, arg) in args.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -106,7 +107,7 @@ pub fn chase_def_use<'a>( let func = env.as_func(ty)?; for (i, arg) in func.args.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -159,7 +160,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result { - for ty in f.args.iter().chain(f.rets.iter()) { + let args = f.args.iter().map(|arg| &arg.typ); + for ty in args.clone().chain(f.rets.iter()) { go(seen, res, _env, ty)?; } } @@ -170,7 +172,7 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result { for arg in args.iter() { - go(seen, res, _env, arg)?; + go(seen, res, _env, &arg.typ)?; } go(seen, res, _env, t)?; } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 99fae6857..8ee44ad0f 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -1,7 +1,7 @@ use super::analysis::{chase_actor, chase_types, infer_rec}; use candid::pretty::candid::pp_mode; use candid::pretty::utils::*; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::types::{ArgType, Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use pretty::RcDoc; use std::collections::BTreeSet; @@ -168,13 +168,14 @@ fn pp_function(func: &Function) -> RcDoc { enclose("(", doc, ")").nest(INDENT_SPACE) } -fn pp_args(args: &[Type]) -> RcDoc { - let doc = concat(args.iter().map(pp_ty), ","); +fn pp_args(args: &[ArgType]) -> RcDoc { + let doc = concat(args.iter().map(|arg| pp_ty(&arg.typ)), ","); enclose("[", doc, "]") } fn pp_rets(args: &[Type]) -> RcDoc { - pp_args(args) + let doc = concat(args.iter().map(pp_ty), ","); + enclose("[", doc, "]") } fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc { @@ -249,11 +250,12 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { let def_list = chase_actor(env, actor).unwrap(); let recs = infer_rec(env, &def_list).unwrap(); let defs = pp_defs(env, &def_list, &recs); - let init = if let TypeInner::Class(ref args, _) = actor.as_ref() { - args.as_slice() + let types = if let TypeInner::Class(ref args, _) = actor.as_ref() { + args.iter().map(|arg| arg.typ.clone()).collect::>() } else { - &[][..] + Vec::new() }; + let init = types.as_slice(); let actor = kwd("return").append(pp_actor(actor, &recs)).append(";"); let body = defs.append(actor); let doc = str("export const idlFactory = ({ IDL }) => ") diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 195e87da3..f225c9ae4 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -4,7 +4,7 @@ use crate::syntax::{self, IDLActorType, IDLMergedProg, IDLType}; use candid::pretty::candid::is_valid_as_id; use candid::pretty::utils::*; -use candid::types::{Field, FuncMode, Function, Label, SharedLabel, Type, TypeInner}; +use candid::types::{ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeInner}; use candid::TypeEnv; use pretty::RcDoc; @@ -185,24 +185,44 @@ fn pp_function(func: &Function) -> RcDoc { } .nest(INDENT_SPACE) } -fn pp_args(args: &[Type]) -> RcDoc { +fn pp_args(args: &[ArgType]) -> RcDoc { match args { [ty] => { - if is_tuple(ty) { - enclose("(", pp_ty(ty), ")") + let typ = if is_tuple(&ty.typ) { + enclose("(", pp_ty(&ty.typ), ")") } else { - pp_ty(ty) + pp_ty(&ty.typ) + }; + if let Some(name) = &ty.name { + enclose("(", escape(name, false).append(" : ").append(typ), ")") + } else { + typ } } _ => { - let doc = concat(args.iter().map(pp_ty), ","); - enclose("(", doc, ")") + let args = args.iter().map(|arg| { + if let Some(name) = &arg.name { + escape(name, false).append(" : ").append(pp_ty(&arg.typ)) + } else { + pp_ty(&arg.typ) + } + }); + enclose("(", concat(args, ","), ")") } } } fn pp_rets(args: &[Type]) -> RcDoc { - pp_args(args) + match args { + [ty] => { + if is_tuple(ty) { + enclose("(", pp_ty(ty), ")") + } else { + pp_ty(ty) + } + } + _ => enclose("(", concat(args.iter().map(pp_ty), ","), ")"), + } } fn pp_service<'a>(serv: &'a [(String, Type)], syntax: Option<&'a [syntax::Binding]>) -> RcDoc<'a> { @@ -280,7 +300,7 @@ fn pp_variant<'a>(fields: &'a [Field], syntax: Option<&'a [syntax::TypeField]>) enclose_space("{", concat(fields, ";"), "}") } -fn pp_class<'a>((args, ty): (&'a [Type], &'a Type), syntax: Option<&'a IDLType>) -> RcDoc<'a> { +fn pp_class<'a>((args, ty): (&'a [ArgType], &'a Type), syntax: Option<&'a IDLType>) -> RcDoc<'a> { let doc = pp_args(args).append(" -> async "); match ty.as_ref() { TypeInner::Service(_) => doc.append(pp_ty_rich(ty, syntax)), diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 9e358bd90..372f1d8cc 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -4,8 +4,8 @@ use crate::{ syntax::{self, IDLActorType, IDLMergedProg, IDLType}, Deserialize, }; -use candid::pretty::utils::*; use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::{pretty::utils::*, types::ArgType}; use convert_case::{Case, Casing}; use pretty::RcDoc; use serde::Serialize; @@ -239,9 +239,15 @@ impl<'a> State<'a> { .map(|(k, v)| (k.clone(), v.clone())) .collect(), ); - let src = candid::pretty::candid::pp_init_args(&env, &[src.clone()]) - .pretty(80) - .to_string(); + let src = candid::pretty::candid::pp_named_init_args( + &env, + &[ArgType { + name: None, + typ: src.clone(), + }], + ) + .pretty(80) + .to_string(); let match_path = self.state.config_source.get("use_type").unwrap().join("."); let test_name = use_type.replace(|c: char| !c.is_ascii_alphanumeric(), "_"); let body = format!( @@ -601,8 +607,18 @@ fn test_{test_name}() {{ } lines(res.into_iter()) } - fn pp_args<'b>(&mut self, args: &'b [Type], prefix: &'b str) -> RcDoc<'b> { - let tys = args.iter().enumerate().map(|(i, t)| { + fn pp_args<'b>(&mut self, args: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { + let doc = args.iter().enumerate().map(|(i, t)| { + let lab = t.name.clone().unwrap_or_else(|| format!("{prefix}{i}")); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self.pp_ty(&t.typ, true); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }); + enclose("(", concat(doc, ","), ")") + } + fn pp_rets<'b>(&mut self, rets: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + let tys = rets.iter().enumerate().map(|(i, t)| { let lab = format!("{prefix}{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); let res = self.pp_ty(t, true); @@ -611,14 +627,11 @@ fn test_{test_name}() {{ }); enclose("(", concat(tys.into_iter(), ","), ")") } - fn pp_rets<'b>(&mut self, rets: &'b [Type]) -> RcDoc<'b> { - self.pp_args(rets, "ret") - } fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { let lab = StateElem::TypeStr("func"); let old = self.state.push_state(&lab); let args = self.pp_args(&f.args, "arg"); - let rets = self.pp_rets(&f.rets); + let rets = self.pp_rets(&f.rets, "ret"); let modes = candid::pretty::candid::pp_modes(&f.modes); let res = args .append(" ->") @@ -668,7 +681,7 @@ fn test_{test_name}() {{ .iter() .enumerate() .map(|(i, arg)| { - let lab = format!("arg{i}"); + let lab = arg.name.clone().unwrap_or_else(|| format!("arg{i}")); let old = self.state.push_state(&StateElem::Label(&lab)); let name = self .state @@ -677,7 +690,7 @@ fn test_{test_name}() {{ .clone() .unwrap_or_else(|| lab.clone()); self.state.update_stats("name"); - let res = self.pp_ty(arg, true); + let res = self.pp_ty(&arg.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); (name, res) }) @@ -727,7 +740,7 @@ fn test_{test_name}() {{ .iter() .enumerate() .map(|(i, arg)| { - let lab = format!("arg{i}"); + let lab = arg.name.clone().unwrap_or_else(|| format!("arg{i}")); let old = self.state.push_state(&StateElem::Label(&lab)); let name = self .state @@ -736,7 +749,7 @@ fn test_{test_name}() {{ .clone() .unwrap_or_else(|| lab.clone()); self.state.update_stats("name"); - let res = self.pp_ty(arg, true); + let res = self.pp_ty(&arg.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); (name, res.pretty(LINE_WIDTH).to_string()) }) @@ -1071,7 +1084,7 @@ impl<'b> NominalState<'_, 'b> { .into_iter() .enumerate() .map(|(i, arg)| { - let lab = format!("arg{i}"); + let lab = arg.name.clone().unwrap_or_else(|| format!("arg{i}")); let old = self.state.push_state(&StateElem::Label(&lab)); let idx = if i == 0 { "".to_string() @@ -1079,10 +1092,13 @@ impl<'b> NominalState<'_, 'b> { i.to_string() }; path.push(TypePath::Func(format!("arg{idx}"))); - let ty = self.nominalize(env, path, &arg, None); + let ty = self.nominalize(env, path, &arg.typ, None); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - ty + ArgType { + name: arg.name.clone(), + typ: ty, + } }) .collect(), rets: func @@ -1168,10 +1184,13 @@ impl<'b> NominalState<'_, 'b> { let elem = StateElem::Label("init"); let old = self.state.push_state(&elem); path.push(TypePath::Init); - let ty = self.nominalize(env, path, arg, None); + let ty = self.nominalize(env, path, &arg.typ, None); path.pop(); self.state.pop_state(old, elem); - ty + ArgType { + name: arg.name.clone(), + typ: ty, + } }) .collect(), self.nominalize(env, path, ty, syntax_ty), diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index d80444f90..6eda10672 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -206,7 +206,7 @@ fn pp_opt<'a>( } fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { - let args = func.args.iter().map(|arg| pp_ty(env, arg, true)); + let args = func.args.iter().map(|arg| pp_ty(env, &arg.typ, true)); let args = enclose("[", concat(args, ","), "]"); let rets = match func.rets.len() { 0 => str("undefined"), diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 584be1729..cfcdd2c73 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -1,7 +1,7 @@ use super::test::{Assert, Input, Test}; -use super::token::{Token, error2, LexicalError, Span, TriviaMap}; +use super::token::{Token, error, error2, LexicalError, Span, TriviaMap}; use candid::{Principal, types::Label}; -use crate::syntax::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLActorType}; +use crate::syntax::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType, IDLActorType}; use candid::types::value::{IDLField, IDLValue, IDLArgs, VariantValue}; use candid::types::{TypeEnv, FuncMode}; use candid::utils::check_unique; @@ -217,16 +217,24 @@ VariantFieldTyp: TypeField = { =>? Ok(TypeField { label: Label::Id(id), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() }), } -TupTyp: Vec = "(" > ")" => <>; +ArgTupTyp: Vec = "(" > ")" =>? { + let args = <>; + let mut named_args: Vec = args.iter().filter_map(|a| a.name.clone()).collect(); + named_args.sort(); + check_unique(named_args.iter()).map_err(|e| error(e))?; + Ok(args) +}; + +TupTyp: Vec = "(" > ")" => <>; FuncTyp: FuncType = { - "->" => + "->" => FuncType { modes, args, rets }, } -ArgTyp: IDLType = { - Typ => <>, - Name ":" => <>, +ArgTyp: IDLArgType = { + => IDLArgType::new(t), + ":" => IDLArgType::new_with_name(t, n), } FuncMode: FuncMode = { @@ -263,7 +271,7 @@ Actor: IDLType = { MainActor: IDLActorType = { "service" "id"? ":" ";"? => IDLActorType { typ: t, docs: doc_comment.unwrap_or_default() }, - "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, + "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, } pub IDLProg: IDLProg = { @@ -271,7 +279,7 @@ pub IDLProg: IDLProg = { } pub IDLInitArgs: IDLInitArgs = { - > => IDLInitArgs { decs, args } + > => IDLInitArgs { decs, args } } // Test file. Follows the "specification" in test/README.md diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 63d3674b4..1a3676bf6 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -69,7 +69,7 @@ //! //! let method = env.get_method(&actor, "g").unwrap(); //! assert_eq!(method.is_query(), true); -//! assert_eq!(method.args, vec![TypeInner::Var("List".to_string()).into()]); +//! assert_eq!(method.args.iter().map(|arg| arg.typ.clone()).collect::>(), vec![TypeInner::Var("List".to_string()).into()]); //! # Ok(()) //! # } //! ``` @@ -102,7 +102,7 @@ //! let method = env.get_method(&actor, "f").unwrap(); //! let args = parse_idl_args("(42, 42, 42, 42)")?; //! // Serialize arguments with candid types -//! let encoded = args.to_bytes_with_types(&env, &method.args)?; +//! let encoded = args.to_bytes_with_types(&env, &method.args.iter().map(|arg| arg.typ.clone()).collect::>())?; //! let decoded = IDLArgs::from_bytes(&encoded)?; //! assert_eq!(decoded.args, //! vec![IDLValue::Nat8(42), diff --git a/rust/candid_parser/src/syntax/mod.rs b/rust/candid_parser/src/syntax/mod.rs index 8a9c51f33..3ff42cdbf 100644 --- a/rust/candid_parser/src/syntax/mod.rs +++ b/rust/candid_parser/src/syntax/mod.rs @@ -20,7 +20,7 @@ pub enum IDLType { RecordT(Vec), VariantT(Vec), ServT(Vec), - ClassT(Vec, Box), + ClassT(Vec, Box), PrincipalT, } @@ -106,10 +106,34 @@ pub enum PrimType { #[derive(Debug, Clone, PartialEq, Eq)] pub struct FuncType { pub modes: Vec, - pub args: Vec, + pub args: Vec, pub rets: Vec, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IDLArgType { + pub typ: IDLType, + pub name: Option, +} + +impl IDLArgType { + pub fn new(typ: IDLType) -> Self { + Self { typ, name: None } + } + + /// Create a new IDLArgType with a name. + /// If the name is an `u32` number, we set it to None + /// as we don't want to use it as a arg name. + pub fn new_with_name(typ: IDLType, name: String) -> Self { + let name = if name.parse::().is_ok() { + None + } else { + Some(name) + }; + Self { typ, name } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeField { pub label: Label, @@ -167,7 +191,7 @@ impl std::str::FromStr for IDLProg { #[derive(Debug)] pub struct IDLInitArgs { pub decs: Vec, - pub args: Vec, + pub args: Vec, } impl std::str::FromStr for IDLInitArgs { diff --git a/rust/candid_parser/src/syntax/pretty.rs b/rust/candid_parser/src/syntax/pretty.rs index 60e6bba7b..bee72c753 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -5,7 +5,9 @@ use crate::{ candid::{pp_docs, pp_label_raw, pp_modes, pp_text}, utils::{concat, enclose, enclose_space, ident, kwd, lines, str, INDENT_SPACE, LINE_WIDTH}, }, - syntax::{Binding, FuncType, IDLActorType, IDLMergedProg, IDLType, PrimType, TypeField}, + syntax::{ + Binding, FuncType, IDLActorType, IDLArgType, IDLMergedProg, IDLType, PrimType, TypeField, + }, }; fn pp_ty(ty: &IDLType) -> RcDoc { @@ -94,13 +96,21 @@ fn pp_method(func: &FuncType) -> RcDoc { .nest(INDENT_SPACE) } -fn pp_args(args: &[IDLType]) -> RcDoc { - let doc = concat(args.iter().map(pp_ty), ","); +fn pp_args(args: &[IDLArgType]) -> RcDoc { + let args = args.iter().map(|arg| { + if let Some(name) = &arg.name { + pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ)) + } else { + pp_ty(&arg.typ) + } + }); + let doc = concat(args, ","); enclose("(", doc, ")") } fn pp_rets(rets: &[IDLType]) -> RcDoc { - pp_args(rets) + let doc = concat(rets.iter().map(pp_ty), ","); + enclose("(", doc, ")") } fn pp_service(methods: &[Binding]) -> RcDoc { @@ -123,7 +133,7 @@ fn pp_service_methods(methods: &[Binding]) -> RcDoc { enclose_space("{", doc, "}") } -fn pp_class<'a>(args: &'a [IDLType], t: &'a IDLType) -> RcDoc<'a> { +fn pp_class<'a>(args: &'a [IDLArgType], t: &'a IDLType) -> RcDoc<'a> { let doc = pp_args(args).append(" ->").append(RcDoc::space()); match t { IDLType::ServT(ref serv) => doc.append(pp_service_methods(serv)), diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index f661dc8ea..2faeea594 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,12 +1,12 @@ use crate::{ pretty_parse, syntax::{ - Binding, Dec, IDLActorType, IDLInitArgs, IDLMergedProg, IDLProg, IDLType, PrimType, - TypeField, + Binding, Dec, IDLActorType, IDLArgType, IDLInitArgs, IDLMergedProg, IDLProg, IDLType, + PrimType, TypeField, }, Error, Result, }; -use candid::types::{Field, Function, Type, TypeEnv, TypeInner}; +use candid::types::{ArgType, Field, Function, Type, TypeEnv, TypeInner}; use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; @@ -74,7 +74,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { IDLType::FuncT(func) => { let mut t1 = Vec::new(); for arg in func.args.iter() { - t1.push(check_type(env, arg)?); + t1.push(check_arg(env, arg)?); } let mut t2 = Vec::new(); for t in func.rets.iter() { @@ -104,6 +104,13 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { } } +fn check_arg(env: &Env, arg: &IDLArgType) -> Result { + Ok(ArgType { + name: arg.name.clone(), + typ: check_type(env, &arg.typ)?, + }) +} + fn check_fields(env: &Env, fs: &[TypeField]) -> Result> { // field label duplication is checked in the parser let mut res = Vec::new(); @@ -193,7 +200,7 @@ fn check_actor(env: &Env, actor: &Option) -> Result> Some(IDLType::ClassT(ts, t)) => { let mut args = Vec::new(); for arg in ts.iter() { - args.push(check_type(env, arg)?); + args.push(check_arg(env, arg)?); } let serv = check_type(env, t)?; env.te.as_service(&serv)?; @@ -262,13 +269,13 @@ pub fn check_init_args( te: &mut TypeEnv, main_env: &TypeEnv, prog: &IDLInitArgs, -) -> Result> { +) -> Result> { let mut env = Env { te, pre: false }; check_decs(&mut env, &prog.decs)?; env.te.merge(main_env)?; let mut args = Vec::new(); for arg in prog.args.iter() { - args.push(check_type(&env, arg)?); + args.push(check_arg(&env, arg)?); } Ok(args) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 1c7df1ca2..0d3ec67b1 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -59,7 +59,10 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; let serv = env.trace_type(&serv)?; Ok(match serv.as_ref() { - TypeInner::Class(args, ty) => (args.clone(), (env, ty.clone())), + TypeInner::Class(args, ty) => ( + args.iter().map(|arg| arg.typ.clone()).collect::>(), + (env, ty.clone()), + ), TypeInner::Service(_) => (vec![], (env, serv)), _ => unreachable!(), }) @@ -115,6 +118,6 @@ pub fn check_rust_type(candid_args: &str) -> Result<()> { let ty = rust_env.add::(); let ty = env.merge_type(rust_env.env, ty); let mut gamma = std::collections::HashSet::new(); - equal(&mut gamma, &env, &args[0], &ty)?; + equal(&mut gamma, &env, &args[0].typ, &ty)?; Ok(()) } diff --git a/rust/candid_parser/tests/assets/collision_arguments.did b/rust/candid_parser/tests/assets/collision_arguments.did new file mode 100644 index 000000000..c7f3117e0 --- /dev/null +++ b/rust/candid_parser/tests/assets/collision_arguments.did @@ -0,0 +1,4 @@ +type f = func (a : nat, b : bool, a : text) -> (); +service : { + f : f; +}; diff --git a/rust/candid_parser/tests/assets/collision_arguments2.did b/rust/candid_parser/tests/assets/collision_arguments2.did new file mode 100644 index 000000000..0a4773fe6 --- /dev/null +++ b/rust/candid_parser/tests/assets/collision_arguments2.did @@ -0,0 +1,3 @@ +service : (b : nat, bool, b : text) -> { + f : (text, text) -> (); +}; diff --git a/rust/candid_parser/tests/assets/ok/class.did b/rust/candid_parser/tests/assets/ok/class.did index 494dcc3bf..a33497f3e 100644 --- a/rust/candid_parser/tests/assets/ok/class.did +++ b/rust/candid_parser/tests/assets/ok/class.did @@ -1,7 +1,7 @@ type Profile = record { age : nat8; name : text }; type List = opt record { int; List }; // Doc comment for class service -service : (int, List, Profile) -> { +service : (int, l : List, Profile) -> { // Doc comment for get method in class service get : () -> (List); set : (List) -> (List); diff --git a/rust/candid_parser/tests/assets/ok/class.mo b/rust/candid_parser/tests/assets/ok/class.mo index 8b9458c7f..454a3f2f8 100644 --- a/rust/candid_parser/tests/assets/ok/class.mo +++ b/rust/candid_parser/tests/assets/ok/class.mo @@ -5,7 +5,7 @@ module { public type List = ?(Int, List); public type Profile = { age : Nat8; name : Text }; /// Doc comment for class service - public type Self = (Int, List, Profile) -> async actor { + public type Self = (Int, l : List, Profile) -> async actor { /// Doc comment for get method in class service get : shared () -> async List; set : shared List -> async List; diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index c48738646..44c590258 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -9,7 +9,7 @@ pub struct List(pub Option<(candid::Int,Box,)>); pub struct Profile { pub age: u8, pub name: String } #[ic_cdk::init] -fn init(arg0: candid::Int, arg1: List, arg2: Profile) { +fn init(arg0: candid::Int, l: List, arg2: Profile) { unimplemented!() } /// Doc comment for get method in class service diff --git a/rust/candid_parser/tests/assets/ok/collision_arguments.fail b/rust/candid_parser/tests/assets/ok/collision_arguments.fail new file mode 100644 index 000000000..9aa36e9cc --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/collision_arguments.fail @@ -0,0 +1 @@ +Candid parser error: label 'a' hash collision with 'a' diff --git a/rust/candid_parser/tests/assets/ok/collision_arguments2.fail b/rust/candid_parser/tests/assets/ok/collision_arguments2.fail new file mode 100644 index 000000000..0c2043a40 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/collision_arguments2.fail @@ -0,0 +1 @@ +Candid parser error: label 'b' hash collision with 'b' diff --git a/rust/candid_parser/tests/assets/ok/example.did b/rust/candid_parser/tests/assets/ok/example.did index 3dd731662..48980d55d 100644 --- a/rust/candid_parser/tests/assets/ok/example.did +++ b/rust/candid_parser/tests/assets/ok/example.did @@ -10,7 +10,7 @@ type List = opt record { type f = func (List, func (int32) -> (int64)) -> (opt List, res); // Doc comment for broker service type broker = service { - find : (text) -> (service { current : () -> (nat32); up : () -> () }); + find : (name : text) -> (service { current : () -> (nat32); up : () -> () }); }; // Doc comment for nested type type nested = record { @@ -77,14 +77,14 @@ type tree = variant { }; // Doc comment for service id type s = service { f : t; g : (list) -> (B, tree, stream) }; -type t = func (s) -> (); +type t = func (server : s) -> (); type stream = opt record { head : nat; next : func () -> (stream) query }; type b = record { int; nat }; type a = variant { a; b : b }; // Doc comment for service service : { // Doc comment for f1 method of service - f1 : (list, blob, opt bool) -> () oneway; + f1 : (list, test : blob, opt bool) -> () oneway; g1 : (my_type, List, opt List, nested) -> (int, broker, nested_res) query; h : (vec opt text, variant { A : nat; B : opt text }, opt List) -> ( record { diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 8dd8d9bf9..1b13151cc 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -15,7 +15,7 @@ module { public type b = (Int, Nat); /// Doc comment for broker service public type broker = actor { - find : shared Text -> async actor { + find : shared (name : Text) -> async actor { current : shared () -> async Nat32; up : shared () -> async (); }; @@ -88,7 +88,7 @@ module { /// Doc comment for service id public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared s -> async (); + public type t = shared (server : s) -> async (); public type tree = { #branch : { val : Int; left : tree; right : tree }; #leaf : Int; @@ -96,7 +96,7 @@ module { /// Doc comment for service public type Self = actor { /// Doc comment for f1 method of service - f1 : shared (list, Blob, ?Bool) -> (); + f1 : shared (list, test : Blob, ?Bool) -> (); g1 : shared query (my_type, List, ?List, nested) -> async ( Int, broker, diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index d296baa01..131b5f62a 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -151,8 +151,8 @@ candid::define_function!(pub(crate) T : (S) -> ()); pub struct Service(pub Principal); impl Service { /// Doc comment for f1 method of service - pub async fn f_1(&self, arg0: &List, arg1: &serde_bytes::ByteBuf, arg2: &Option) -> Result<()> { - ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await + pub async fn f_1(&self, arg0: &List, test: &serde_bytes::ByteBuf, arg2: &Option) -> Result<()> { + ic_cdk::call(self.0, "f1", (arg0,test,arg2,)).await } pub async fn G11(&self, id: &CanisterId, list: &MyList, is_okay: &Option, arg3: &Nested) -> Result<(i128,Broker,NestedRes,)> { ic_cdk::call(self.0, "g1", (id,list,is_okay,arg3,)).await @@ -170,8 +170,8 @@ impl Service { pub async fn y(&self, arg0: &NestedRecords) -> Result<((NestedRecords,MyVariant,),)> { ic_cdk::call(self.0, "y", (arg0,)).await } - pub async fn f(&self, arg0: &S) -> Result<()> { - ic_cdk::call(self.0, "f", (arg0,)).await + pub async fn f(&self, server: &S) -> Result<()> { + ic_cdk::call(self.0, "f", (server,)).await } pub async fn g(&self, arg0: &List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.did b/rust/candid_parser/tests/assets/ok/fieldnat.did index 7855c696a..1f5e2a585 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.did +++ b/rust/candid_parser/tests/assets/ok/fieldnat.did @@ -1,7 +1,7 @@ type tuple = record { text; text }; type non_tuple = record { 1 : text; 2 : text }; service : { - bab : (int, nat) -> (); + bab : (two : int, nat) -> (); bar : (record { "2" : int }) -> (variant { e20; e30 }); bas : (record { int; int }) -> (record { text; nat }); baz : (record { 2 : int; "2" : nat }) -> (record {}); diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.mo b/rust/candid_parser/tests/assets/ok/fieldnat.mo index 64f771834..f38c0359f 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.mo +++ b/rust/candid_parser/tests/assets/ok/fieldnat.mo @@ -5,7 +5,7 @@ module { public type non_tuple = { _1_ : Text; _2_ : Text }; public type tuple = (Text, Text); public type Self = actor { - bab : shared (Int, Nat) -> async (); + bab : shared (two : Int, Nat) -> async (); bar : shared { _50_ : Int } -> async { #e20; #e30 }; bas : shared ((Int, Int)) -> async ((Text, Nat)); baz : shared { _2_ : Int; _50_ : Nat } -> async {}; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index b5399f7f8..43c063fd1 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -29,8 +29,8 @@ pub struct FooRet { pub _2_: candid::Int, pub _2: candid::Int } pub struct Service(pub Principal); impl Service { - pub async fn bab(&self, arg0: &candid::Int, arg1: &candid::Nat) -> Result<()> { - ic_cdk::call(self.0, "bab", (arg0,arg1,)).await + pub async fn bab(&self, two: &candid::Int, arg1: &candid::Nat) -> Result<()> { + ic_cdk::call(self.0, "bab", (two,arg1,)).await } pub async fn bar(&self, arg0: &BarArg) -> Result<(BarRet,)> { ic_cdk::call(self.0, "bar", (arg0,)).await diff --git a/rust/candid_parser/tests/assets/ok/keyword.did b/rust/candid_parser/tests/assets/ok/keyword.did index 0a42d407c..9b1d2cec1 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.did +++ b/rust/candid_parser/tests/assets/ok/keyword.did @@ -6,7 +6,7 @@ type if = variant { leaf : int; }; type return = service { f : t; g : (list) -> (if, stream) }; -type t = func (return) -> (); +type t = func (server : return) -> (); type stream = opt record { head : nat; next : func () -> (stream) query }; service : { Oneway : () -> () oneway; diff --git a/rust/candid_parser/tests/assets/ok/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo index 2802968e2..408ff6f82 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.mo +++ b/rust/candid_parser/tests/assets/ok/keyword.mo @@ -11,7 +11,7 @@ module { public type o = ?o; public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared return_ -> async (); + public type t = shared (server : return_) -> async (); public type Self = actor { Oneway : shared () -> (); f__ : shared o -> async o; diff --git a/rust/candid_parser/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index 7da7385aa..d25756994 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.rs +++ b/rust/candid_parser/tests/assets/ok/keyword.rs @@ -66,8 +66,8 @@ impl Service { pub async fn r#return(&self, arg0: &O) -> Result<(O,)> { ic_cdk::call(self.0, "return", (arg0,)).await } - pub async fn service(&self, arg0: &Return) -> Result<()> { - ic_cdk::call(self.0, "service", (arg0,)).await + pub async fn service(&self, server: &Return) -> Result<()> { + ic_cdk::call(self.0, "service", (server,)).await } pub async fn tuple(&self, arg0: &(candid::Int,serde_bytes::ByteBuf,String,)) -> Result<((candid::Int,u8,),)> { ic_cdk::call(self.0, "tuple", (arg0,)).await diff --git a/rust/candid_parser/tests/assets/ok/recursion.did b/rust/candid_parser/tests/assets/ok/recursion.did index 9137077e8..4dd70d2e7 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.did +++ b/rust/candid_parser/tests/assets/ok/recursion.did @@ -8,6 +8,6 @@ type tree = variant { }; // Doc comment for service id type s = service { f : t; g : (list) -> (B, tree, stream) }; -type t = func (s) -> (); +type t = func (server : s) -> (); type stream = opt record { head : nat; next : func () -> (stream) query }; service : s diff --git a/rust/candid_parser/tests/assets/ok/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo index cdb3eb519..b23248144 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.mo +++ b/rust/candid_parser/tests/assets/ok/recursion.mo @@ -9,7 +9,7 @@ module { /// Doc comment for service id public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared s -> async (); + public type t = shared (server : s) -> async (); public type tree = { #branch : { val : Int; left : tree; right : tree }; #leaf : Int; diff --git a/rust/candid_parser/tests/assets/ok/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs index 3b21c9c3e..239310042 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.rs +++ b/rust/candid_parser/tests/assets/ok/recursion.rs @@ -31,8 +31,8 @@ candid::define_service!(pub S : { pub struct Service(pub Principal); impl Service { - pub async fn f(&self, arg0: &S) -> Result<()> { - ic_cdk::call(self.0, "f", (arg0,)).await + pub async fn f(&self, server: &S) -> Result<()> { + ic_cdk::call(self.0, "f", (server,)).await } pub async fn g(&self, arg0: &List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index a860f6bb7..4ce9d0d09 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -42,7 +42,16 @@ service : { let method = env.get_method(&actor, "f").unwrap(); { let args = parse_idl_args("(42,42,42,42)").unwrap(); - let encoded = args.to_bytes_with_types(&env, &method.args).unwrap(); + let encoded = args + .to_bytes_with_types( + &env, + &method + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(), + ) + .unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!( decoded.args, diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index f22ce6ca8..c6e69114d 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -140,10 +140,9 @@ impl TypeAnnotation { .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; let func = env.get_method(&actor, meth)?; let types = match mode { - Mode::Encode => &func.args, - Mode::Decode => &func.rets, - } - .clone(); + Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), + Mode::Decode => func.rets.clone(), + }; Ok((env, types)) } _ => unreachable!(), From fe06e737fc3140c6701e0a7fed7adb3893f7bc53 Mon Sep 17 00:00:00 2001 From: David Frank Date: Tue, 21 Jan 2025 19:32:25 +0100 Subject: [PATCH 02/11] feat!: apply #590 to next branch --------- Co-authored-by: Linwei Shang Co-authored-by: David Frank --- CHANGELOG.md | 7 + Cargo.lock | 27 +- clippy.toml | 1 + rust/candid/Cargo.toml | 2 + rust/candid/src/binary_parser.rs | 13 +- rust/candid/src/lib.rs | 2 +- rust/candid/src/pretty/candid.rs | 8 +- rust/candid/src/types/internal.rs | 33 ++- rust/candid/src/types/mod.rs | 1 + rust/candid/src/types/type_env.rs | 54 ++-- rust/candid/src/types/type_key.rs | 230 ++++++++++++++++++ rust/candid_parser/src/bindings/analysis.rs | 13 +- rust/candid_parser/src/bindings/javascript.rs | 14 +- rust/candid_parser/src/bindings/motoko.rs | 6 +- rust/candid_parser/src/bindings/rust.rs | 48 ++-- rust/candid_parser/src/bindings/typescript.rs | 14 +- rust/candid_parser/src/configs.rs | 2 +- rust/candid_parser/src/lib.rs | 2 +- rust/candid_parser/src/random.rs | 2 +- rust/candid_parser/src/typing.rs | 14 +- rust/candid_parser/src/utils.rs | 5 +- rust/candid_parser/tests/parse_type.rs | 3 +- 22 files changed, 403 insertions(+), 98 deletions(-) create mode 100644 clippy.toml create mode 100644 rust/candid/src/types/type_key.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 638120980..7f1ddcad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ # Changelog + ## Unreleased +### Candid + +* [BREAKING]: type representation was optimized to improve performance: + * In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + * `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. + ## 2025-07-29 ### Candid 0.10.16 diff --git a/Cargo.lock b/Cargo.lock index 305c27b29..090f0116d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "0.6.15" @@ -217,6 +223,8 @@ dependencies = [ "byteorder", "candid_derive 0.10.16", "candid_parser 0.2.1", + "foldhash", + "hashbrown 0.15.2", "hex", "ic_principal 0.1.1", "leb128", @@ -607,6 +615,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "generic-array" version = "0.14.7" @@ -674,6 +688,17 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.5.0" @@ -729,7 +754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..db551b3a9 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +ignore-interior-mutability = ["candid::types::type_key::TypeKey"] diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index aa2b3cf88..1d41e1e5c 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -20,6 +20,8 @@ candid_derive = { path = "../candid_derive", version = "=0.10.16" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" +foldhash = "0.1.3" +hashbrown = "0.15" leb128 = "0.2.5" paste = "1.0" hex.workspace = true diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index ca8cd1636..1c53a8e39 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -1,4 +1,5 @@ -use crate::types::internal::{ArgType, Field, Function, Label, Type, TypeInner}; +use crate::types::internal::{ArgType, Field, Function, Label, Type, TypeInner, TypeKey}; +use crate::types::type_env::TypeMap; use crate::types::{FuncMode, TypeEnv}; use anyhow::{anyhow, Context, Result}; use binread::io::{Read, Seek}; @@ -135,9 +136,6 @@ pub struct PrincipalBytes { pub inner: Vec, } -fn index_to_var(ind: i64) -> String { - format!("table{ind}") -} impl IndexType { fn to_type(&self, len: u64) -> Result { Ok(match self.index { @@ -145,7 +143,7 @@ impl IndexType { if v >= len as i64 { return Err(anyhow!("type index {} out of range", v)); } - TypeInner::Var(index_to_var(v)) + TypeInner::Var(TypeKey::indexed(v)) } -1 => TypeInner::Null, -2 => TypeInner::Bool, @@ -236,13 +234,12 @@ impl ConsType { } impl Table { fn to_env(&self, len: u64) -> Result { - use std::collections::BTreeMap; - let mut env = BTreeMap::new(); + let mut env = TypeMap::default(); for (i, t) in self.table.iter().enumerate() { let ty = t .to_type(len) .with_context(|| format!("Invalid table entry {i}: {t:?}"))?; - env.insert(index_to_var(i as i64), ty); + env.insert(TypeKey::indexed(i as i64), ty); } // validate method has func type for t in env.values() { diff --git a/rust/candid/src/lib.rs b/rust/candid/src/lib.rs index 75ca3c057..57e38781a 100644 --- a/rust/candid/src/lib.rs +++ b/rust/candid/src/lib.rs @@ -303,7 +303,7 @@ pub mod pretty; // Candid hash function comes from // https://caml.inria.fr/pub/papers/garrigue-polymorphic_variants-ml98.pdf -// Not public API. Only used by tests. +// Not public API. // Remember to update the same function in candid_derive if you change this function. #[doc(hidden)] #[inline] diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 0c2733fff..8396e0305 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -99,7 +99,7 @@ pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { Text => str("text"), Reserved => str("reserved"), Empty => str("empty"), - Var(ref s) => str(s), + Var(ref s) => str(s.as_str()), Principal => str("principal"), Opt(ref t) => kwd("opt").append(pp_ty(t)), Vec(ref t) if matches!(t.as_ref(), Nat8) => str("blob"), @@ -224,9 +224,9 @@ fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> } fn pp_defs(env: &TypeEnv) -> RcDoc { - lines(env.0.iter().map(|(id, ty)| { + lines(env.to_sorted_iter().map(|(id, ty)| { kwd("type") - .append(ident(id)) + .append(ident(id.as_str())) .append(kwd("=")) .append(pp_ty(ty)) .append(";") @@ -237,7 +237,7 @@ fn pp_class<'a>(args: &'a [ArgType], t: &'a Type, docs: Option<&'a DocComments>) let doc = pp_named_args(args).append(" ->").append(RcDoc::space()); match t.as_ref() { TypeInner::Service(ref serv) => doc.append(pp_service(serv, docs)), - TypeInner::Var(ref s) => doc.append(s), + TypeInner::Var(ref s) => doc.append(s.as_str()), _ => unreachable!(), } } diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 4a70cdba0..62d8ae855 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -1,9 +1,14 @@ use super::CandidType; use crate::idl_hash; +use crate::types::type_env::TypeMap; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt; +use std::fmt::Debug; +use std::hash::Hash; + +pub use crate::types::type_key::TypeKey; // This is a re-implementation of std::any::TypeId to get rid of 'static constraint. // The current TypeId doesn't consider lifetime while computing the hash, which is @@ -25,7 +30,7 @@ impl TypeId { impl std::fmt::Display for TypeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = NAME.with(|n| n.borrow_mut().get(self)); - write!(f, "{name}") + f.write_str(&name) } } pub fn type_of(_: &T) -> TypeId { @@ -114,8 +119,9 @@ impl TypeContainer { } let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - self.env.0.insert(id.to_string(), res); - TypeInner::Var(id.to_string()) + let type_key = TypeKey::from(id.to_string()); + self.env.0.insert(type_key.clone(), res); + TypeInner::Var(type_key) } else { // if the type is part of an enum, the id won't be recorded. // we want to inline the type in this case. @@ -134,17 +140,18 @@ impl TypeContainer { .into(); let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - self.env.0.insert(id.to_string(), res); - TypeInner::Var(id.to_string()) + let type_key = TypeKey::from(id.to_string()); + self.env.0.insert(type_key.clone(), res); + TypeInner::Var(type_key) } else { return res; } } TypeInner::Knot(id) => { - let name = id.to_string(); let ty = ENV.with(|e| e.borrow().get(id).unwrap().clone()); - self.env.0.insert(id.to_string(), ty); - TypeInner::Var(name) + let type_key = TypeKey::from(id.to_string()); + self.env.0.insert(type_key.clone(), ty); + TypeInner::Var(type_key) } TypeInner::Func(func) => TypeInner::Func(Function { modes: func.modes.clone(), @@ -202,7 +209,7 @@ pub enum TypeInner { Reserved, Empty, Knot(TypeId), // For recursive types from Rust - Var(String), // For variables from Candid file + Var(TypeKey), // For variables from Candid file Unknown, Opt(Type), Vec(Type), @@ -263,12 +270,12 @@ impl Type { pub fn is_blob(&self, env: &crate::TypeEnv) -> bool { self.as_ref().is_blob(env) } - pub fn subst(&self, tau: &std::collections::BTreeMap) -> Self { + pub fn subst(&self, tau: &TypeMap) -> Self { use TypeInner::*; match self.as_ref() { Var(id) => match tau.get(id) { - None => Var(id.to_string()), - Some(new_id) => Var(new_id.to_string()), + None => Var(id.clone()), + Some(new_id) => Var(new_id.clone()), }, Opt(t) => Opt(t.subst(tau)), Vec(t) => Vec(t.subst(tau)), @@ -360,7 +367,7 @@ pub fn text_size(t: &Type, limit: i32) -> Result { Reserved => 8, Principal => 9, Knot(_) => 10, - Var(id) => id.len() as i32, + Var(id) => id.as_str().len() as i32, Opt(t) => 4 + text_size(t, limit - 4)?, Vec(t) => 4 + text_size(t, limit - 4)?, Record(fs) | Variant(fs) => { diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 72b385c90..84a4b9a78 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -29,6 +29,7 @@ pub mod result; pub mod arc; pub mod rc; +mod type_key; pub trait CandidType { // memoized type derivation diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 0723f2769..5f5aef1ce 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,17 +1,22 @@ +use crate::types::internal::TypeKey; use crate::types::{Function, Type, TypeInner}; use crate::{Error, Result}; -use std::collections::BTreeMap; +use foldhash::fast::FixedState; +use hashbrown::HashMap; + +pub type TypeMap = HashMap; #[derive(Debug, Clone, Default)] -pub struct TypeEnv(pub BTreeMap); +pub struct TypeEnv(pub TypeMap); impl TypeEnv { pub fn new() -> Self { - TypeEnv(BTreeMap::new()) + TypeEnv(TypeMap::default()) } + pub fn merge<'a>(&'a mut self, env: &TypeEnv) -> Result<&'a mut Self> { - for (k, v) in &env.0 { - let entry = self.0.entry(k.to_string()).or_insert_with(|| v.clone()); + for (k, v) in env.0.iter() { + let entry = self.0.entry(k.clone()).or_insert_with(|| v.clone()); if *entry != *v { return Err(Error::msg("inconsistent binding")); } @@ -19,11 +24,11 @@ impl TypeEnv { Ok(self) } pub fn merge_type(&mut self, env: TypeEnv, ty: Type) -> Type { - let tau: BTreeMap = env + let tau: TypeMap = env .0 .keys() .filter(|k| self.0.contains_key(*k)) - .map(|k| (k.clone(), format!("{k}/1"))) + .map(|k| (k.clone(), format!("{k}/1").into())) .collect(); for (k, t) in env.0 { let t = t.subst(&tau); @@ -35,13 +40,14 @@ impl TypeEnv { } ty.subst(&tau) } - pub fn find_type(&self, name: &str) -> Result<&Type> { - match self.0.get(name) { - None => Err(Error::msg(format!("Unbound type identifier {name}"))), + pub fn find_type(&self, key: &TypeKey) -> Result<&Type> { + match self.0.get(key) { + None => Err(Error::msg(format!("Unbound type identifier {key}"))), Some(t) => Ok(t), } } - pub fn rec_find_type(&self, name: &str) -> Result<&Type> { + + pub fn rec_find_type(&self, name: &TypeKey) -> Result<&Type> { let t = self.find_type(name)?; match t.as_ref() { TypeInner::Var(id) => self.rec_find_type(id), @@ -82,8 +88,8 @@ impl TypeEnv { } fn is_empty<'a>( &'a self, - res: &mut BTreeMap<&'a str, Option>, - id: &'a str, + res: &mut HashMap<&'a TypeKey, Option, FixedState>, + id: &'a TypeKey, ) -> Result { match res.get(id) { None => { @@ -116,24 +122,34 @@ impl TypeEnv { } } pub fn replace_empty(&mut self) -> Result<()> { - let mut res = BTreeMap::new(); - for name in self.0.keys() { - self.is_empty(&mut res, name)?; + let mut res = HashMap::default(); + for key in self.0.keys() { + self.is_empty(&mut res, key)?; } let ids: Vec<_> = res - .iter() + .into_iter() .filter(|(_, v)| matches!(v, Some(true))) - .map(|(id, _)| id.to_string()) + .map(|(id, _)| id.to_owned()) .collect(); for id in ids { self.0.insert(id, TypeInner::Empty.into()); } Ok(()) } + + /// Creates an iterator that iterates over the types in the order of keys. + /// + /// The implementation collects elements into a temporary vector and sorts the vector. + pub fn to_sorted_iter(&self) -> impl Iterator { + let mut vec: Vec<_> = self.0.iter().collect(); + vec.sort_unstable_by_key(|elem| elem.0); + vec.into_iter() + } } + impl std::fmt::Display for TypeEnv { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for (k, v) in &self.0 { + for (k, v) in self.to_sorted_iter() { writeln!(f, "type {k} = {v}")?; } Ok(()) diff --git a/rust/candid/src/types/type_key.rs b/rust/candid/src/types/type_key.rs new file mode 100644 index 000000000..238f37b29 --- /dev/null +++ b/rust/candid/src/types/type_key.rs @@ -0,0 +1,230 @@ +use crate::idl_hash; +use std::cell::OnceCell; +use std::cmp::Ordering; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; + +#[derive(Clone)] +enum TypeKeyInner { + Indexed { index: i64, name: OnceCell }, + Named { name: String, hash: u32 }, +} + +impl TypeKeyInner { + pub fn as_str(&self) -> &str { + match self { + TypeKeyInner::Indexed { index, name } => { + name.get_or_init(|| TypeKey::name_from_index(*index)) + } + TypeKeyInner::Named { name, .. } => name, + } + } +} + +impl Debug for TypeKeyInner { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// A `TypeKey` represents a type from a Candid schema. The type can be string-based or index-based. +#[derive(Debug, Clone)] +pub struct TypeKey(TypeKeyInner); + +impl TypeKey { + const INDEXED_PREFIX: &'static str = "table"; + + pub fn indexed(idx: i64) -> Self { + TypeKeyInner::Indexed { + index: idx, + name: Default::default(), + } + .into() + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + fn name_from_index(i: i64) -> String { + format!("{}{}", Self::INDEXED_PREFIX, i) + } + + fn maybe_indexed(value: &str) -> Option { + let index = i64::from_str(value.strip_prefix(Self::INDEXED_PREFIX)?).ok()?; + Some(TypeKey::indexed(index)) + } +} + +impl From for TypeKey { + fn from(value: TypeKeyInner) -> Self { + Self(value) + } +} + +impl PartialOrd for TypeKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TypeKey { + fn cmp(&self, other: &Self) -> Ordering { + // If the performance of this function ever becomes too slow, the function can be optimized + // by specializing comparison of two TypeKeyInner::Indexed objects. + self.as_str().cmp(other.as_str()) + } +} + +impl PartialEq for TypeKey { + fn eq(&self, other: &Self) -> bool { + match (&self.0, &other.0) { + ( + TypeKeyInner::Indexed { index: index1, .. }, + TypeKeyInner::Indexed { index: index2, .. }, + ) => *index1 == *index2, + ( + TypeKeyInner::Named { + hash: hash1, + name: name1, + }, + TypeKeyInner::Named { + hash: hash2, + name: name2, + }, + ) => *hash1 == *hash2 && name1 == name2, + _ => false, + } + } +} + +impl Eq for TypeKey {} + +impl Hash for TypeKey { + fn hash(&self, state: &mut H) { + match &self.0 { + TypeKeyInner::Indexed { index, .. } => index.hash(state), + TypeKeyInner::Named { hash, .. } => hash.hash(state), + } + } +} + +impl Display for TypeKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl From for TypeKey { + fn from(value: String) -> Self { + Self::maybe_indexed(&value).unwrap_or_else(|| { + TypeKeyInner::Named { + hash: idl_hash(&value), + name: value, + } + .into() + }) + } +} + +impl From<&str> for TypeKey { + fn from(value: &str) -> Self { + Self::maybe_indexed(value).unwrap_or_else(|| { + TypeKeyInner::Named { + hash: idl_hash(value), + name: value.to_string(), + } + .into() + }) + } +} + +impl AsRef for TypeKey { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn type_key_indexed_name() { + assert_eq!(TypeKey::indexed(42).as_str(), "table42"); + } + + #[test] + fn type_key_from_string() { + assert_eq!(TypeKey::from("foobar"), TypeKey::from("foobar".to_string())); + assert_eq!(TypeKey::from("foobar").as_str(), "foobar"); + assert_eq!(TypeKey::from("table").as_str(), "table"); + // Check that indexed keys with a "broken" index are parsed correctly. + assert_eq!(TypeKey::from("table3.5").as_str(), "table3.5"); + assert_eq!(TypeKey::from("table-33").as_str(), "table-33"); + } + + #[test] + fn type_key_indexed() { + assert_eq!(TypeKey::from("table33"), TypeKey::indexed(33)); + } + + #[test] + fn type_key_hash() { + let mut map = HashMap::new(); + map.insert(TypeKey::from("a"), 1); + map.insert(TypeKey::from("bar".to_string()), 2); + map.insert(TypeKey::from("table1"), 3); + map.insert(TypeKey::from("table97"), 4); + map.insert(TypeKey::indexed(33), 5); + + assert_eq!(map.get(&"a".to_string().into()), Some(&1)); + assert_eq!(map.get(&"bar".into()), Some(&2)); + assert_eq!(map.get(&TypeKey::indexed(1)), Some(&3)); + assert_eq!(map.get(&TypeKey::indexed(97)), Some(&4)); + assert_eq!(map.get(&TypeKey::from("table33")), Some(&5)); + } + + #[test] + fn type_key_ord() { + let mut keys = vec![ + TypeKey::indexed(4), + TypeKey::indexed(24), + TypeKey::from("table3"), + TypeKey::from("table"), + TypeKey::indexed(1), + TypeKey::from("table23"), + TypeKey::from("table4.3"), + TypeKey::from("zzz"), + TypeKey::from("a"), + ]; + + let expected = vec![ + TypeKey::from("a"), + TypeKey::from("table"), + TypeKey::indexed(1), + TypeKey::from("table23"), + // Note that even though 3 < 24 and 4 < 24, they're ordered afterward because we use + // string-based ordering to maintain consistency between named and indexed TypeKeys. + TypeKey::indexed(24), + TypeKey::from("table3"), + TypeKey::indexed(4), + TypeKey::from("table4.3"), + TypeKey::from("zzz"), + ]; + keys.sort_unstable(); + assert_eq!(keys, expected); + } + + #[test] + fn type_key_to_string() { + assert_eq!(TypeKey::indexed(32).to_string(), "table32"); + assert_eq!(TypeKey::from("foo").to_string(), "foo"); + // debug string + assert_eq!(format!("{:?}", TypeKey::indexed(32)), "TypeKey(table32)"); + assert_eq!(format!("{:?}", TypeKey::from("foo")), "TypeKey(foo)"); + } +} diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index bf80a0524..01af68de9 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -1,4 +1,5 @@ use crate::{Error, Result}; +use candid::types::internal::TypeKey; use candid::types::{Type, TypeEnv, TypeInner}; use std::collections::{BTreeMap, BTreeSet}; @@ -43,10 +44,10 @@ pub fn chase_type<'a>( use TypeInner::*; match t.as_ref() { Var(id) => { - if seen.insert(id) { + if seen.insert(id.as_str()) { let t = env.find_type(id)?; chase_type(seen, res, env, t)?; - res.push(id); + res.push(id.as_str()); } } Opt(ty) | Vec(ty) => chase_type(seen, res, env, ty)?, @@ -149,8 +150,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result { - if seen.insert(id) { - res.insert(id); + if seen.insert(id.as_str()) { + res.insert(id.as_str()); } } Opt(ty) | Vec(ty) => go(seen, res, _env, ty)?, @@ -180,8 +181,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result RcDoc { Text => str("IDL.Text"), Reserved => str("IDL.Reserved"), Empty => str("IDL.Empty"), - Var(ref s) => ident(s), + Var(ref s) => ident(s.as_str()), Principal => str("IDL.Principal"), Opt(ref t) => str("IDL.Opt").append(enclose("(", pp_ty(t), ")")), Vec(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), @@ -206,8 +206,8 @@ fn pp_defs<'a>( recs.iter() .map(|id| kwd("const").append(ident(id)).append(" = IDL.Rec();")), ); - let defs = lines(def_list.iter().map(|id| { - let ty = env.find_type(id).unwrap(); + let defs = lines(def_list.iter().map(|&id| { + let ty = env.find_type(&id.into()).unwrap(); if recs.contains(id) { ident(id) .append(".fill") @@ -227,10 +227,10 @@ fn pp_actor<'a>(ty: &'a Type, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { match ty.as_ref() { TypeInner::Service(_) => pp_ty(ty), TypeInner::Var(id) => { - if recs.contains(&*id.clone()) { - str(id).append(".getType()") + if recs.contains(id.as_str()) { + str(id.as_str()).append(".getType()") } else { - str(id) + str(id.as_str()) } } TypeInner::Class(_, t) => pp_actor(t, recs), @@ -241,7 +241,7 @@ fn pp_actor<'a>(ty: &'a Type, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { pub fn compile(env: &TypeEnv, actor: &Option) -> String { match actor { None => { - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); + let def_list: Vec<_> = env.to_sorted_iter().map(|pair| pair.0.as_str()).collect(); let recs = infer_rec(env, &def_list).unwrap(); let doc = pp_defs(env, &def_list, &recs); doc.pretty(LINE_WIDTH).to_string() diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index f225c9ae4..35f369899 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -138,7 +138,7 @@ fn pp_ty(ty: &Type) -> RcDoc { Text => str("Text"), Reserved => str("Any"), Empty => str("None"), - Var(ref s) => escape(s, false), + Var(ref s) => escape(s.as_str(), false), Principal => str("Principal"), Opt(ref t) => str("?").append(pp_ty(t)), Vec(ref t) => pp_vec(t, None), @@ -315,12 +315,12 @@ fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { fn pp_defs<'a>(env: &'a TypeEnv, prog: &'a IDLMergedProg) -> RcDoc<'a> { lines(env.0.iter().map(|(id, ty)| { - let syntax = prog.lookup(id); + let syntax = prog.lookup(id.as_str()); let docs = syntax .map(|b| pp_docs(b.docs.as_ref())) .unwrap_or(RcDoc::nil()); docs.append(kwd("public type")) - .append(escape(id, false)) + .append(escape(id.as_str(), false)) .append(" = ") .append(pp_ty_rich(ty, syntax.map(|b| &b.typ))) .append(";") diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 372f1d8cc..70b0ff88b 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -4,7 +4,9 @@ use crate::{ syntax::{self, IDLActorType, IDLMergedProg, IDLType}, Deserialize, }; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::types::{ + internal::TypeKey, Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner, +}; use candid::{pretty::utils::*, types::ArgType}; use convert_case::{Case, Casing}; use pretty::RcDoc; @@ -15,7 +17,7 @@ use std::collections::{BTreeMap, BTreeSet}; const DOC_COMMENT_PREFIX: &str = "/// "; /// Maps the names generated during nominalization to the original syntactic types. -type GeneratedTypes<'a> = BTreeMap; +type GeneratedTypes<'a> = BTreeMap; #[derive(Default, Deserialize, Clone, Debug)] pub struct BindingConfig { @@ -292,7 +294,7 @@ fn test_{test_name}() {{ Text => str("String"), Reserved => str("candid::Reserved"), Empty => str("candid::Empty"), - Var(ref id) => self.pp_var(id, is_ref), + Var(ref id) => self.pp_var(id.as_str(), is_ref), Principal => str("Principal"), Opt(ref t) => self.pp_opt(t, is_ref), // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` @@ -504,13 +506,14 @@ fn test_{test_name}() {{ fn pp_defs(&mut self, def_list: &'a [&'a str]) -> RcDoc<'a> { let mut res = Vec::with_capacity(def_list.len()); - for id in def_list { + for &id in def_list { let old = self.state.push_state(&StateElem::Label(id)); if self.state.config.use_type.is_some() { self.state.pop_state(old, StateElem::Label(id)); continue; } - let ty = self.state.env.find_type(id).unwrap(); + let type_key: TypeKey = id.into(); + let ty = self.state.env.find_type(&type_key).unwrap(); let name = self .state .config @@ -521,7 +524,7 @@ fn test_{test_name}() {{ let syntax = self.prog.lookup(id); let syntax_ty = syntax .map(|b| &b.typ) - .or_else(|| self.generated_types.get(*id).map(|t| &**t)); + .or_else(|| self.generated_types.get(&type_key).map(|t| &**t)); let docs = syntax .map(|b| pp_docs(b.docs.as_ref())) .unwrap_or(RcDoc::nil()); @@ -807,7 +810,9 @@ pub fn emit_bindgen( let def_list = if let Some(actor) = &actor { chase_actor(&env, actor).unwrap() } else { - env.0.iter().map(|pair| pair.0.as_ref()).collect::>() + env.to_sorted_iter() + .map(|pair| pair.0.as_str()) + .collect::>() }; let recs = infer_rec(&env, &def_list).unwrap(); let mut state = State { @@ -1022,11 +1027,12 @@ impl<'b> NominalState<'_, 'b> { &TypeInner::Record(fs.to_vec()).into(), syntax, ); + let new_var: TypeKey = new_var.into(); env.0.insert(new_var.clone(), ty); if let Some(syntax) = syntax { self.generated_types.insert(new_var.clone(), syntax); } - TypeInner::Var(new_var) + TypeInner::Var(new_var.into()) } } TypeInner::Variant(fs) => { @@ -1067,11 +1073,12 @@ impl<'b> NominalState<'_, 'b> { &TypeInner::Variant(fs.to_vec()).into(), syntax, ); + let new_var: TypeKey = new_var.into(); env.0.insert(new_var.clone(), ty); if let Some(syntax) = syntax { self.generated_types.insert(new_var.clone(), syntax); } - TypeInner::Var(new_var) + TypeInner::Var(new_var.into()) } } TypeInner::Func(func) => match path.last() { @@ -1136,8 +1143,8 @@ impl<'b> NominalState<'_, 'b> { &TypeInner::Func(func.clone()).into(), None, ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.0.insert(new_var.clone().into(), ty); + TypeInner::Var(new_var.into()) } }, TypeInner::Service(serv) => match path.last() { @@ -1168,8 +1175,8 @@ impl<'b> NominalState<'_, 'b> { &TypeInner::Service(serv.clone()).into(), None, ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.0.insert(new_var.clone().into(), ty); + TypeInner::Var(new_var.into()) } }, TypeInner::Class(args, ty) => { @@ -1211,12 +1218,17 @@ impl<'b> NominalState<'_, 'b> { prog: &'b IDLMergedProg, ) -> (TypeEnv, Option) { let mut res = TypeEnv(Default::default()); - for (id, ty) in self.state.env.0.iter() { - let elem = StateElem::Label(id); + for (id, ty) in self.state.env.to_sorted_iter() { + let elem = StateElem::Label(id.as_str()); let old = self.state.push_state(&elem); - let syntax = prog.lookup(id).map(|t| &t.typ); - let ty = self.nominalize(&mut res, &mut vec![TypePath::Id(id.clone())], ty, syntax); - res.0.insert(id.to_string(), ty); + let syntax = prog.lookup(id.as_str()).map(|t| &t.typ); + let ty = self.nominalize( + &mut res, + &mut vec![TypePath::Id(id.to_string())], + ty, + syntax, + ); + res.0.insert(id.clone(), ty); self.state.pop_state(old, elem); } let actor = actor diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 6eda10672..b1e915f85 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -77,7 +77,7 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { if is_ref && matches!(ty.as_ref(), Service(_)) { return pp_inline_service(); } - ident(id) + ident(id.as_str()) } Principal => str("Principal"), Opt(ref t) => pp_opt(env, t, None, is_ref), @@ -238,7 +238,7 @@ fn pp_service<'a>( } let func = match func.as_ref() { TypeInner::Func(ref func) => pp_function(env, func), - TypeInner::Var(ref id) => ident(id), + TypeInner::Var(ref id) => ident(id.as_str()), _ => unreachable!(), }; docs.append(quote_ident(id)).append(kwd(":")).append(func) @@ -263,8 +263,8 @@ fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { } fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str], prog: &'a IDLMergedProg) -> RcDoc<'a> { - lines(def_list.iter().map(|id| { - let ty = env.find_type(id).unwrap(); + lines(def_list.iter().map(|&id| { + let ty = env.find_type(&id.into()).unwrap(); let syntax = prog.lookup(id); let syntax_ty = syntax.map(|s| &s.typ); let docs = syntax @@ -287,7 +287,7 @@ fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str], prog: &'a IDLMergedPro TypeInner::Var(ref inner_id) => kwd("export type") .append(ident(id)) .append(" = ") - .append(ident(inner_id)) + .append(ident(inner_id.as_str())) .append(";"), _ => kwd("export type") .append(ident(id)) @@ -305,7 +305,7 @@ fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type, syntax: Option<&'a IDLType>) -> TypeInner::Service(_) => service_doc.append(pp_ty_rich(env, ty, syntax, false)), TypeInner::Var(id) => service_doc .append(kwd("extends")) - .append(str(id)) + .append(str(id.as_str())) .append(str(" {}")), TypeInner::Class(_, t) => { if let Some(IDLType::ClassT(_, syntax_t)) = syntax { @@ -324,7 +324,7 @@ import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; "#; let syntax_actor = prog.resolve_actor().ok().flatten(); - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); + let def_list: Vec<_> = env.to_sorted_iter().map(|pair| pair.0.as_str()).collect(); let defs = pp_defs(env, &def_list, prog); let actor = match actor { None => RcDoc::nil(), diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 4b6a57cde..e50d80a19 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -393,7 +393,7 @@ fn path_name(t: &Type) -> String { TypeInner::Text => "text", TypeInner::Reserved => "reserved", TypeInner::Empty => "empty", - TypeInner::Var(id) => id, + TypeInner::Var(id) => id.as_str(), TypeInner::Knot(id) => id.name, TypeInner::Principal => "principal", TypeInner::Opt(_) => "opt", diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 1a3676bf6..bf579c8f4 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -69,7 +69,7 @@ //! //! let method = env.get_method(&actor, "g").unwrap(); //! assert_eq!(method.is_query(), true); -//! assert_eq!(method.args.iter().map(|arg| arg.typ.clone()).collect::>(), vec![TypeInner::Var("List".to_string()).into()]); +//! assert_eq!(method.args.iter().map(|arg| arg.typ.clone()).collect::>(), vec![TypeInner::Var("List".into()).into()]); //! # Ok(()) //! # } //! ``` diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 8a38e586d..31ca3bbdf 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -300,7 +300,7 @@ fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option Result { match t { IDLType::PrimT(prim) => Ok(check_prim(prim)), IDLType::VarT(id) => { - env.te.find_type(id)?; - Ok(TypeInner::Var(id.to_string()).into()) + let key = id.as_str().into(); + env.te.find_type(&key)?; + Ok(TypeInner::Var(key).into()) } IDLType::OptT(t) => { let t = check_type(env, t)?; @@ -146,7 +147,7 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { match dec { Dec::TypD(Binding { id, typ, docs: _ }) => { let t = check_type(env, typ)?; - env.te.0.insert(id.to_string(), t); + env.te.0.insert(id.clone().into(), t); } Dec::ImportType(_) | Dec::ImportServ(_) => (), } @@ -158,7 +159,7 @@ fn check_cycle(env: &TypeEnv) -> Result<()> { fn has_cycle<'a>(seen: &mut BTreeSet<&'a str>, env: &'a TypeEnv, t: &'a Type) -> Result { match t.as_ref() { TypeInner::Var(id) => { - if seen.insert(id) { + if seen.insert(id.as_str()) { let ty = env.find_type(id)?; has_cycle(seen, env, ty) } else { @@ -180,7 +181,10 @@ fn check_cycle(env: &TypeEnv) -> Result<()> { fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, .. }) = dec { - let duplicate = env.te.0.insert(id.to_string(), TypeInner::Unknown.into()); + let duplicate = env + .te + .0 + .insert(id.as_str().into(), TypeInner::Unknown.into()); if duplicate.is_some() { return Err(Error::msg(format!("duplicate binding for {id}"))); } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 0d3ec67b1..d6e69aa51 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,4 +1,5 @@ use crate::{check_prog, pretty_check_file, pretty_parse, Error, Result}; +use candid::types::internal::TypeKey; use candid::{ types::{Type, TypeInner}, TypeEnv, @@ -78,8 +79,8 @@ pub fn get_metadata(env: &TypeEnv, serv: &Option) -> Option { let def_list = crate::bindings::analysis::chase_actor(env, &serv).ok()?; let mut filtered = TypeEnv::new(); for d in def_list { - if let Some(t) = env.0.get(d) { - filtered.0.insert(d.to_string(), t.clone()); + if let Some(t) = env.0.get(&TypeKey::from(d)) { + filtered.0.insert(d.to_string().into(), t.clone()); } } Some(candid::pretty::candid::compile(&filtered, &Some(serv))) diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 4bf218ce7..ee6351366 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -7,6 +7,7 @@ use candid_parser::{ }; use goldenfile::Mint; use std::io::Write; +use std::panic::AssertUnwindSafe; use std::path::Path; #[test] @@ -134,7 +135,7 @@ fn compiler_test(resource: &str) { { match filename.file_name().unwrap().to_str().unwrap() { "unicode.did" | "escape.did" => check_error( - || motoko::compile(&env, &actor, &prog), + AssertUnwindSafe(|| motoko::compile(&env, &actor, &prog)), "not a valid Motoko id", ), _ => { From eef379ecc7f4d970027085ead26c5e9555532bba Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 29 Jul 2025 20:30:42 +0200 Subject: [PATCH 03/11] chore: update changelog --- CHANGELOG.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f1ddcad5..2bc14230e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,40 @@ # Changelog - ## Unreleased ### Candid * [BREAKING]: type representation was optimized to improve performance: - * In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. - * `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. + + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. + + The `args` field of the `candid::types::internal::Function` struct now is a `Vec` instead of `Vec`, to preserve argument names. + + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. -## 2025-07-29 +* Non-breaking changes: + + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. -### Candid 0.10.16 +### candid_parser * Breaking changes: - + The `args` field of the `candid::types::internal::Function` struct now is a `Vec` instead of `Vec`, to preserve argument names. - + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. + + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + +* Non-breaking changes: + + Supports parsing the arguments' names for `func` and `service` (init args). + +## 2025-07-29 + +### Candid 0.10.16 * Non-breaking changes: + Makes the warning message for the special opt subtyping rule more explicit in the `candid::types::subtype::subtype` and `candid::types::subtype::subtype_with_config` functions. - + Added `pp_named_args`, `pp_named_init_args` and `pp_label_raw` in `pretty::candid` module. + + Added `pp_label_raw` in `pretty::candid` module. ### candid_parser 0.2.1 * Breaking changes: + The `candid_parser::types` module has been renamed to `candid_parser::syntax`. - + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. * Non-breaking changes: - + Supports parsing the arguments' names for `func` and `service` (init args). + Supports collecting line comments as doc comments in the following cases: - above services: ``` From ad5f6eec2df399fa57664ee0971d7d5ead1e8fa8 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 29 Jul 2025 20:32:28 +0200 Subject: [PATCH 04/11] build: update bench Cargo.lock --- rust/bench/Cargo.lock | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 4a67e53c0..3ac12e37f 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.86" @@ -153,6 +159,8 @@ dependencies = [ "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown 0.15.4", "hex", "ic_principal", "leb128", @@ -397,6 +405,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -440,6 +454,17 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hex" version = "0.4.3" @@ -511,7 +536,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] From 434e6f39021961af27071872c9813e8f4525b7db Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 8 Aug 2025 17:26:25 +0200 Subject: [PATCH 05/11] test: update motoko test cases --- rust/candid_parser/tests/assets/ok/actor.mo | 4 +- rust/candid_parser/tests/assets/ok/cyclic.mo | 6 +- rust/candid_parser/tests/assets/ok/example.mo | 82 +++++++++---------- .../tests/assets/ok/inline_methods.mo | 2 +- rust/candid_parser/tests/assets/ok/keyword.mo | 6 +- .../tests/assets/ok/management.mo | 50 +++++------ .../tests/assets/ok/recursion.mo | 10 +-- rust/candid_parser/tests/assets/ok/service.mo | 2 +- 8 files changed, 81 insertions(+), 81 deletions(-) diff --git a/rust/candid_parser/tests/assets/ok/actor.mo b/rust/candid_parser/tests/assets/ok/actor.mo index 98531f3fc..46d5a4332 100644 --- a/rust/candid_parser/tests/assets/ok/actor.mo +++ b/rust/candid_parser/tests/assets/ok/actor.mo @@ -2,10 +2,10 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type f = shared Int8 -> async Int8; public type g = f; - public type h = shared f -> async f; public type o = ?o; + public type f = shared Int8 -> async Int8; + public type h = shared f -> async f; public type Self = actor { f : shared Nat -> async h; g : f; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.mo b/rust/candid_parser/tests/assets/ok/cyclic.mo index 4ca4a600a..7bae9418e 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.mo +++ b/rust/candid_parser/tests/assets/ok/cyclic.mo @@ -3,10 +3,10 @@ module { public type A = ?B; - public type B = ?C; - public type C = A; + public type Z = A; public type X = Y; + public type C = A; public type Y = Z; - public type Z = A; + public type B = ?C; public type Self = actor { f : shared (A, B, C, X, Y, Z) -> async () } } diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 1b13151cc..13381c772 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -2,8 +2,26 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type A = B; - public type B = ?A; + public type my_variant = { + /// Doc comment for my_variant field a + #a : { + /// Doc comment for my_variant field a field b + b : Text; + }; + /// Doc comment for my_variant field c + #c : ?{ + /// Doc comment for my_variant field c field d + d : Text; + e : [ + { + /// Doc comment for my_variant field c field e inner vec element + f : Nat; + } + ]; + }; + }; + /// Doc comment for service id + public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; /// Doc comment for List public type List = ?{ /// Doc comment for List head @@ -11,8 +29,12 @@ module { /// Doc comment for List tail tail : List; }; + public type list = ?node; + public type tree = { + #branch : { val : Int; left : tree; right : tree }; + #leaf : Int; + }; public type a = { #a; #b : b }; - public type b = (Int, Nat); /// Doc comment for broker service public type broker = actor { find : shared (name : Text) -> async actor { @@ -20,31 +42,16 @@ module { up : shared () -> async (); }; }; + public type t = shared (server : s) -> async (); + /// Doc comment for prim type + public type my_type = Principal; public type f = shared (List, shared Int32 -> async Int64) -> async ( ?List, res, ); - public type list = ?node; - /// Doc comment for prim type - public type my_type = Principal; - public type my_variant = { - /// Doc comment for my_variant field a - #a : { - /// Doc comment for my_variant field a field b - b : Text; - }; - /// Doc comment for my_variant field c - #c : ?{ - /// Doc comment for my_variant field c field d - d : Text; - e : [ - { - /// Doc comment for my_variant field c field e inner vec element - f : Nat; - } - ]; - }; - }; + public type b = (Int, Nat); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; + public type A = B; /// Doc comment for nested type public type nested = { _0_ : Nat; @@ -56,14 +63,6 @@ module { _41_ : { #_42_ ; #A; #B; #C }; _42_ : Nat; }; - /// Doc comment for nested_records - public type nested_records = { - /// Doc comment for nested_records field nested - nested : ?{ - /// Doc comment for nested_records field nested_field - nested_field : Text; - }; - }; public type nested_res = { #Ok : { #Ok; #Err }; #Err : { @@ -73,7 +72,15 @@ module { #Err : { _0_ : Int }; }; }; - public type node = { head : Nat; tail : list }; + /// Doc comment for nested_records + public type nested_records = { + /// Doc comment for nested_records field nested + nested : ?{ + /// Doc comment for nested_records field nested_field + nested_field : Text; + }; + }; + public type B = ?A; /// Doc comment for res type public type res = { /// Doc comment for Ok variant @@ -85,14 +92,7 @@ module { error : Text; }; }; - /// Doc comment for service id - public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared (server : s) -> async (); - public type tree = { - #branch : { val : Int; left : tree; right : tree }; - #leaf : Int; - }; + public type node = { head : Nat; tail : list }; /// Doc comment for service public type Self = actor { /// Doc comment for f1 method of service diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.mo b/rust/candid_parser/tests/assets/ok/inline_methods.mo index b1c8b0168..f97f4ecc6 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.mo +++ b/rust/candid_parser/tests/assets/ok/inline_methods.mo @@ -3,9 +3,9 @@ module { public type Fn = shared query Nat -> async Nat; - public type Gn = Fn; public type R = { x : Nat; fn : Fn; gn : Gn; nested : { fn : Gn } }; public type RInline = { x : Nat; fn : shared query Nat -> async Nat }; + public type Gn = Fn; public type Self = actor { add_two : shared Nat -> async Nat; fn : Fn; diff --git a/rust/candid_parser/tests/assets/ok/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo index 408ff6f82..cf214b121 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.mo +++ b/rust/candid_parser/tests/assets/ok/keyword.mo @@ -2,16 +2,16 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type if_ = { #branch : { val : Int; left : if_; right : if_ }; #leaf : Int; }; + public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; public type list = ?node; - public type node = { head : Nat; tail : list }; public type o = ?o; - public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type t = shared (server : return_) -> async (); + public type node = { head : Nat; tail : list }; public type Self = actor { Oneway : shared () -> (); f__ : shared o -> async o; diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 0609c7387..83fc90749 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -3,34 +3,41 @@ module { public type bitcoin_address = Text; - public type bitcoin_network = { #mainnet; #testnet }; - public type block_hash = Blob; - public type canister_id = Principal; + public type ecdsa_curve = { #secp256k1 }; + public type get_utxos_request = { + network : bitcoin_network; + filter : ?{ #page : Blob; #min_confirmations : Nat32 }; + address : bitcoin_address; + }; public type canister_settings = { freezing_threshold : ?Nat; controllers : ?[Principal]; memory_allocation : ?Nat; compute_allocation : ?Nat; }; - public type definite_canister_settings = { - freezing_threshold : Nat; - controllers : [Principal]; - memory_allocation : Nat; - compute_allocation : Nat; + public type user_id = Principal; + public type get_current_fee_percentiles_request = { + network : bitcoin_network; }; - public type ecdsa_curve = { #secp256k1 }; + public type outpoint = { txid : Blob; vout : Nat32 }; public type get_balance_request = { network : bitcoin_network; address : bitcoin_address; min_confirmations : ?Nat32; }; - public type get_current_fee_percentiles_request = { - network : bitcoin_network; + public type definite_canister_settings = { + freezing_threshold : Nat; + controllers : [Principal]; + memory_allocation : Nat; + compute_allocation : Nat; }; - public type get_utxos_request = { + public type satoshi = Nat64; + public type bitcoin_network = { #mainnet; #testnet }; + public type millisatoshi_per_byte = Nat64; + public type wasm_module = Blob; + public type send_transaction_request = { + transaction : Blob; network : bitcoin_network; - filter : ?{ #page : Blob; #min_confirmations : Nat32 }; - address : bitcoin_address; }; public type get_utxos_response = { next_page : ?Blob; @@ -38,22 +45,15 @@ module { tip_block_hash : block_hash; utxos : [utxo]; }; - public type http_header = { value : Text; name : Text }; + public type block_hash = Blob; + public type canister_id = Principal; + public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; public type http_response = { status : Nat; body : Blob; headers : [http_header]; }; - public type millisatoshi_per_byte = Nat64; - public type outpoint = { txid : Blob; vout : Nat32 }; - public type satoshi = Nat64; - public type send_transaction_request = { - transaction : Blob; - network : bitcoin_network; - }; - public type user_id = Principal; - public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; - public type wasm_module = Blob; + public type http_header = { value : Text; name : Text }; public type Self = actor { bitcoin_get_balance : shared get_balance_request -> async satoshi; bitcoin_get_current_fee_percentiles : shared get_current_fee_percentiles_request -> async [ diff --git a/rust/candid_parser/tests/assets/ok/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo index b23248144..34e72f02b 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.mo +++ b/rust/candid_parser/tests/assets/ok/recursion.mo @@ -2,17 +2,17 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type A = B; - public type B = ?A; - public type list = ?node; - public type node = { head : Nat; tail : list }; /// Doc comment for service id public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared (server : s) -> async (); + public type B = ?A; + public type list = ?node; public type tree = { #branch : { val : Int; left : tree; right : tree }; #leaf : Int; }; + public type t = shared (server : s) -> async (); + public type node = { head : Nat; tail : list }; public type Self = s } diff --git a/rust/candid_parser/tests/assets/ok/service.mo b/rust/candid_parser/tests/assets/ok/service.mo index 14ee15e7a..ff6c34865 100644 --- a/rust/candid_parser/tests/assets/ok/service.mo +++ b/rust/candid_parser/tests/assets/ok/service.mo @@ -2,8 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type Func = shared () -> async Service; public type Service = actor { f : Func }; + public type Func = shared () -> async Service; public type Service2 = Service; public type Self = actor { asArray : shared query () -> async ([Service2], [Func]); From 4767ae20164b7afec949e4761948b6dc5296edf7 Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 11 Aug 2025 09:21:51 +0200 Subject: [PATCH 06/11] chore: fixes new clippy lints (#673) Looks like a recent bump of the Rust version on CI introduced new Clippy lints that make CI fail on #668 This PR fixes those lint errors on the `next` branch. --- rust/candid/src/pretty/candid.rs | 36 +++++++++---------- rust/candid/src/pretty/utils.rs | 8 ++--- rust/candid_parser/src/bindings/javascript.rs | 30 ++++++++-------- rust/candid_parser/src/bindings/motoko.rs | 12 +++---- rust/candid_parser/src/bindings/rust.rs | 8 ++--- rust/candid_parser/src/bindings/typescript.rs | 2 +- rust/candid_parser/src/syntax/pretty.rs | 30 ++++++++-------- rust/candid_parser/tests/value.rs | 5 +-- tools/didc-js/wasm-package/src/core.rs | 4 ++- 9 files changed, 69 insertions(+), 66 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 8396e0305..48a2b4770 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -71,15 +71,15 @@ fn ident_string(id: &str) -> String { } } -pub fn pp_text(id: &str) -> RcDoc { +pub fn pp_text(id: &str) -> RcDoc<'_> { RcDoc::text(ident_string(id)) } -pub fn pp_ty(ty: &Type) -> RcDoc { +pub fn pp_ty(ty: &Type) -> RcDoc<'_> { pp_ty_inner(ty.as_ref()) } -pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { +pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc<'_> { use TypeInner::*; match ty { Null => str("null"), @@ -130,18 +130,18 @@ pub fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { /// This function is kept for backward compatibility. /// /// It is recommended to use [`pp_label_raw`] instead, which accepts a [`Label`]. -pub fn pp_label(id: &SharedLabel) -> RcDoc { +pub fn pp_label(id: &SharedLabel) -> RcDoc<'_> { pp_label_raw(id.as_ref()) } -pub fn pp_label_raw(id: &Label) -> RcDoc { +pub fn pp_label_raw(id: &Label) -> RcDoc<'_> { match id { Label::Named(id) => pp_text(id), Label::Id(_) | Label::Unnamed(_) => RcDoc::as_string(id), } } -pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { +pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc<'_> { let ty_doc = if is_variant && *field.ty == TypeInner::Null { RcDoc::nil() } else { @@ -150,12 +150,12 @@ pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { pp_label_raw(&field.id).append(ty_doc) } -fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc { +fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { let fields = fs.iter().map(|f| pp_field(f, is_variant)); enclose_space("{", concat(fields, ";"), "}") } -pub fn pp_function(func: &Function) -> RcDoc { +pub fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_named_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -168,7 +168,7 @@ pub fn pp_function(func: &Function) -> RcDoc { /// Pretty-prints named arguments in the form of `(name1 : type1, name2 : type2)`. /// /// To print unnamed arguments, use [`pp_args`] instead. -pub fn pp_named_args(args: &[ArgType]) -> RcDoc { +pub fn pp_named_args(args: &[ArgType]) -> RcDoc<'_> { let args = args.iter().map(|arg| { if let Some(name) = &arg.name { pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ)) @@ -183,24 +183,24 @@ pub fn pp_named_args(args: &[ArgType]) -> RcDoc { /// Pretty-prints arguments in the form of `(type1, type2)`. /// /// To print named arguments, use [`pp_named_args`] instead. -pub fn pp_args(args: &[Type]) -> RcDoc { +pub fn pp_args(args: &[Type]) -> RcDoc<'_> { let doc = concat(args.iter().map(pp_ty), ","); enclose("(", doc, ")") } /// Pretty-prints return types in the form of `(type1, type2)`. -pub fn pp_rets(args: &[Type]) -> RcDoc { +pub fn pp_rets(args: &[Type]) -> RcDoc<'_> { pp_args(args) } -pub fn pp_mode(mode: &FuncMode) -> RcDoc { +pub fn pp_mode(mode: &FuncMode) -> RcDoc<'_> { match mode { FuncMode::Oneway => RcDoc::text("oneway"), FuncMode::Query => RcDoc::text("query"), FuncMode::CompositeQuery => RcDoc::text("composite_query"), } } -pub fn pp_modes(modes: &[FuncMode]) -> RcDoc { +pub fn pp_modes(modes: &[FuncMode]) -> RcDoc<'_> { RcDoc::concat(modes.iter().map(|m| RcDoc::space().append(pp_mode(m)))) } @@ -223,7 +223,7 @@ fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> enclose_space("{", doc, "}") } -fn pp_defs(env: &TypeEnv) -> RcDoc { +fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { lines(env.to_sorted_iter().map(|(id, ty)| { kwd("type") .append(ident(id.as_str())) @@ -523,7 +523,7 @@ pub mod value { } } - fn pp_field(depth: usize, field: &IDLField, is_variant: bool) -> RcDoc { + fn pp_field(depth: usize, field: &IDLField, is_variant: bool) -> RcDoc<'_> { let val_doc = if is_variant && field.val == IDLValue::Null { RcDoc::nil() } else { @@ -532,7 +532,7 @@ pub mod value { pp_label_raw(&field.id).append(val_doc) } - fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc { + fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc<'_> { let fs = concat(fields.iter().map(|f| pp_field(depth, f, false)), ";"); enclose_space("{", fs, "}") } @@ -545,7 +545,7 @@ pub mod value { format!("\\{v:02x}") } } - pub fn pp_value(depth: usize, v: &IDLValue) -> RcDoc { + pub fn pp_value(depth: usize, v: &IDLValue) -> RcDoc<'_> { use IDLValue::*; if depth == 0 { return RcDoc::as_string(format!("{v:?}")); @@ -579,7 +579,7 @@ pub mod value { } } - pub fn pp_args(args: &IDLArgs) -> RcDoc { + pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> { let args = args .args .iter() diff --git a/rust/candid/src/pretty/utils.rs b/rust/candid/src/pretty/utils.rs index 6127d34cd..9e1a911ef 100644 --- a/rust/candid/src/pretty/utils.rs +++ b/rust/candid/src/pretty/utils.rs @@ -71,19 +71,19 @@ where RcDoc::concat(docs.map(|doc| doc.append(RcDoc::hardline()))) } -pub fn kwd(str: &U) -> RcDoc { +pub fn kwd(str: &U) -> RcDoc<'_> { RcDoc::as_string(str).append(RcDoc::space()) } -pub fn str(str: &str) -> RcDoc { +pub fn str(str: &str) -> RcDoc<'_> { RcDoc::text(str) } -pub fn ident(id: &str) -> RcDoc { +pub fn ident(id: &str) -> RcDoc<'_> { kwd(id) } -pub fn quote_ident(id: &str) -> RcDoc { +pub fn quote_ident(id: &str) -> RcDoc<'_> { str("'") .append(format!("{}", id.escape_debug())) .append("'") diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 5df9ef38a..78fad89fd 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -91,7 +91,7 @@ static KEYWORDS: [&str; 64] = [ "with", "yield", ]; -pub(crate) fn ident(id: &str) -> RcDoc { +pub(crate) fn ident(id: &str) -> RcDoc<'_> { if KEYWORDS.contains(&id) { str(id).append("_") } else { @@ -99,7 +99,7 @@ pub(crate) fn ident(id: &str) -> RcDoc { } } -fn pp_ty(ty: &Type) -> RcDoc { +fn pp_ty(ty: &Type) -> RcDoc<'_> { use TypeInner::*; match ty.as_ref() { Null => str("IDL.Null"), @@ -139,7 +139,7 @@ fn pp_ty(ty: &Type) -> RcDoc { } } -fn pp_label(id: &SharedLabel) -> RcDoc { +fn pp_label(id: &SharedLabel) -> RcDoc<'_> { match &**id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") @@ -149,18 +149,18 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field(field: &Field) -> RcDoc { +fn pp_field(field: &Field) -> RcDoc<'_> { pp_label(&field.id) .append(kwd(":")) .append(pp_ty(&field.ty)) } -fn pp_fields(fs: &[Field]) -> RcDoc { +fn pp_fields(fs: &[Field]) -> RcDoc<'_> { let fields = concat(fs.iter().map(pp_field), ","); enclose_space("({", fields, "})") } -fn pp_function(func: &Function) -> RcDoc { +fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -168,17 +168,17 @@ fn pp_function(func: &Function) -> RcDoc { enclose("(", doc, ")").nest(INDENT_SPACE) } -fn pp_args(args: &[ArgType]) -> RcDoc { +fn pp_args(args: &[ArgType]) -> RcDoc<'_> { let doc = concat(args.iter().map(|arg| pp_ty(&arg.typ)), ","); enclose("[", doc, "]") } -fn pp_rets(args: &[Type]) -> RcDoc { +fn pp_rets(args: &[Type]) -> RcDoc<'_> { let doc = concat(args.iter().map(pp_ty), ","); enclose("[", doc, "]") } -fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc { +fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc<'_> { let doc = concat( modes .iter() @@ -188,7 +188,7 @@ fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc { enclose("[", doc, "]") } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[(String, Type)]) -> RcDoc<'_> { let doc = concat( serv.iter() .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))), @@ -298,7 +298,7 @@ pub mod value { _ => false, } } - fn pp_label(id: &Label) -> RcDoc { + fn pp_label(id: &Label) -> RcDoc<'_> { match id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") @@ -307,17 +307,17 @@ pub mod value { .append(RcDoc::space()), } } - fn pp_field(field: &IDLField) -> RcDoc { + fn pp_field(field: &IDLField) -> RcDoc<'_> { pp_label(&field.id) .append(": ") .append(pp_value(&field.val)) } - fn pp_fields(fields: &[IDLField]) -> RcDoc { + fn pp_fields(fields: &[IDLField]) -> RcDoc<'_> { concat(fields.iter().map(pp_field), ",") } - pub fn pp_value(v: &IDLValue) -> RcDoc { + pub fn pp_value(v: &IDLValue) -> RcDoc<'_> { use IDLValue::*; match v { Number(_) | Int(_) | Nat(_) | Int64(_) | Nat64(_) => { @@ -362,7 +362,7 @@ pub mod value { } } - pub fn pp_args(args: &IDLArgs) -> RcDoc { + pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> { let body = concat(args.args.iter().map(pp_value), ","); enclose("[", body, "]") } diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 35f369899..46fbe098f 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -78,7 +78,7 @@ static KEYWORDS: [&str; 48] = [ "while", "with", ]; -fn escape(id: &str, is_method: bool) -> RcDoc { +fn escape(id: &str, is_method: bool) -> RcDoc<'_> { if KEYWORDS.contains(&id) { str(id).append("_") } else if is_valid_as_id(id) { @@ -118,7 +118,7 @@ fn pp_ty_rich<'a>(ty: &'a Type, syntax: Option<&'a IDLType>) -> RcDoc<'a> { } } -fn pp_ty(ty: &Type) -> RcDoc { +fn pp_ty(ty: &Type) -> RcDoc<'_> { use TypeInner::*; match ty.as_ref() { Null => str("Null"), @@ -151,7 +151,7 @@ fn pp_ty(ty: &Type) -> RcDoc { } } -fn pp_label(id: &SharedLabel) -> RcDoc { +fn pp_label(id: &SharedLabel) -> RcDoc<'_> { match &**id { Label::Named(str) => escape(str, false), Label::Id(n) | Label::Unnamed(n) => str("_") @@ -161,7 +161,7 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_function(func: &Function) -> RcDoc { +fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); match func.modes.as_slice() { @@ -185,7 +185,7 @@ fn pp_function(func: &Function) -> RcDoc { } .nest(INDENT_SPACE) } -fn pp_args(args: &[ArgType]) -> RcDoc { +fn pp_args(args: &[ArgType]) -> RcDoc<'_> { match args { [ty] => { let typ = if is_tuple(&ty.typ) { @@ -212,7 +212,7 @@ fn pp_args(args: &[ArgType]) -> RcDoc { } } -fn pp_rets(args: &[Type]) -> RcDoc { +fn pp_rets(args: &[Type]) -> RcDoc<'_> { match args { [ty] => { if is_tuple(ty) { diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 70b0ff88b..f789f21c8 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -131,7 +131,7 @@ static KEYWORDS: [&str; 51] = [ "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try", ]; -fn ident_(id: &str, case: Option) -> (RcDoc, bool) { +fn ident_(id: &str, case: Option) -> (RcDoc<'_>, bool) { if id.is_empty() || id.starts_with(|c: char| !c.is_ascii_alphabetic() && c != '_') || id.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') @@ -152,7 +152,7 @@ fn ident_(id: &str, case: Option) -> (RcDoc, bool) { (RcDoc::text(id), is_rename) } } -fn ident(id: &str, case: Option) -> RcDoc { +fn ident(id: &str, case: Option) -> RcDoc<'_> { ident_(id, case).0 } fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { @@ -1032,7 +1032,7 @@ impl<'b> NominalState<'_, 'b> { if let Some(syntax) = syntax { self.generated_types.insert(new_var.clone(), syntax); } - TypeInner::Var(new_var.into()) + TypeInner::Var(new_var) } } TypeInner::Variant(fs) => { @@ -1078,7 +1078,7 @@ impl<'b> NominalState<'_, 'b> { if let Some(syntax) = syntax { self.generated_types.insert(new_var.clone(), syntax); } - TypeInner::Var(new_var.into()) + TypeInner::Var(new_var) } } TypeInner::Func(func) => match path.last() { diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index b1e915f85..3f8aaf094 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -99,7 +99,7 @@ fn pp_inline_service<'a>() -> RcDoc<'a> { str("Principal") } -fn pp_label(id: &SharedLabel) -> RcDoc { +fn pp_label(id: &SharedLabel) -> RcDoc<'_> { match &**id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") diff --git a/rust/candid_parser/src/syntax/pretty.rs b/rust/candid_parser/src/syntax/pretty.rs index bee72c753..98b898e61 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -10,7 +10,7 @@ use crate::{ }, }; -fn pp_ty(ty: &IDLType) -> RcDoc { +fn pp_ty(ty: &IDLType) -> RcDoc<'_> { use IDLType::*; match ty { PrimT(PrimType::Null) => str("null"), @@ -42,7 +42,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { } } -fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc { +fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc<'_> { let docs = pp_docs(&field.docs); let ty_doc = if is_variant && field.typ == IDLType::PrimT(PrimType::Null) { RcDoc::nil() @@ -52,16 +52,16 @@ fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc { docs.append(pp_label_raw(&field.label)).append(ty_doc) } -fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc { +fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc<'_> { let fields = fs.iter().map(|f| pp_field(f, is_variant)); enclose_space("{", concat(fields, ";"), "}") } -fn pp_opt(ty: &IDLType) -> RcDoc { +fn pp_opt(ty: &IDLType) -> RcDoc<'_> { kwd("opt").append(pp_ty(ty)) } -fn pp_vec(ty: &IDLType) -> RcDoc { +fn pp_vec(ty: &IDLType) -> RcDoc<'_> { if matches!(ty, IDLType::PrimT(PrimType::Nat8)) { str("blob") } else { @@ -69,7 +69,7 @@ fn pp_vec(ty: &IDLType) -> RcDoc { } } -fn pp_record(fs: &[TypeField], is_tuple: bool) -> RcDoc { +fn pp_record(fs: &[TypeField], is_tuple: bool) -> RcDoc<'_> { if is_tuple { let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ";"); kwd("record").append(enclose_space("{", tuple, "}")) @@ -78,15 +78,15 @@ fn pp_record(fs: &[TypeField], is_tuple: bool) -> RcDoc { } } -fn pp_variant(fs: &[TypeField]) -> RcDoc { +fn pp_variant(fs: &[TypeField]) -> RcDoc<'_> { kwd("variant").append(pp_fields(fs, true)) } -fn pp_function(func: &FuncType) -> RcDoc { +fn pp_function(func: &FuncType) -> RcDoc<'_> { kwd("func").append(pp_method(func)) } -fn pp_method(func: &FuncType) -> RcDoc { +fn pp_method(func: &FuncType) -> RcDoc<'_> { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -96,7 +96,7 @@ fn pp_method(func: &FuncType) -> RcDoc { .nest(INDENT_SPACE) } -fn pp_args(args: &[IDLArgType]) -> RcDoc { +fn pp_args(args: &[IDLArgType]) -> RcDoc<'_> { let args = args.iter().map(|arg| { if let Some(name) = &arg.name { pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ)) @@ -108,16 +108,16 @@ fn pp_args(args: &[IDLArgType]) -> RcDoc { enclose("(", doc, ")") } -fn pp_rets(rets: &[IDLType]) -> RcDoc { +fn pp_rets(rets: &[IDLType]) -> RcDoc<'_> { let doc = concat(rets.iter().map(pp_ty), ","); enclose("(", doc, ")") } -fn pp_service(methods: &[Binding]) -> RcDoc { +fn pp_service(methods: &[Binding]) -> RcDoc<'_> { kwd("service").append(pp_service_methods(methods)) } -fn pp_service_methods(methods: &[Binding]) -> RcDoc { +fn pp_service_methods(methods: &[Binding]) -> RcDoc<'_> { let methods = methods.iter().map(|b| { let docs = pp_docs(&b.docs); let func_doc = match b.typ { @@ -142,7 +142,7 @@ fn pp_class<'a>(args: &'a [IDLArgType], t: &'a IDLType) -> RcDoc<'a> { } } -fn pp_defs(prog: &IDLMergedProg) -> RcDoc { +fn pp_defs(prog: &IDLMergedProg) -> RcDoc<'_> { lines(prog.bindings().map(|b| { let docs = pp_docs(&b.docs); docs.append(kwd("type")) @@ -153,7 +153,7 @@ fn pp_defs(prog: &IDLMergedProg) -> RcDoc { })) } -fn pp_actor(actor: &IDLActorType) -> RcDoc { +fn pp_actor(actor: &IDLActorType) -> RcDoc<'_> { let docs = pp_docs(&actor.docs); let service_doc = match actor.typ { IDLType::ServT(ref serv) => pp_service_methods(serv), diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index 4ce9d0d09..0d2f522f4 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -7,6 +7,7 @@ use candid::{ Decode, }; use candid_parser::{parse_idl_args, syntax::IDLProg, typing::check_prog}; +use std::slice; #[test] fn test_parser() { @@ -130,7 +131,7 @@ fn test_variant() { )); let bytes = hex("4449444c016b02b3d3c9017fe6fdd5017f010000"); test_decode(&bytes, &value); - let encoded = IDLArgs::new(&[value.clone()]).to_bytes().unwrap(); + let encoded = IDLArgs::new(slice::from_ref(&value)).to_bytes().unwrap(); test_decode(&encoded, &value); } @@ -153,7 +154,7 @@ fn check(v: IDLValue, bytes: &str) { } fn test_encode(v: &IDLValue, expected: &[u8]) { - let args = IDLArgs::new(&[v.clone()]); + let args = IDLArgs::new(slice::from_ref(v)); let encoded = args.to_bytes().unwrap(); assert_eq!( encoded, expected, diff --git a/tools/didc-js/wasm-package/src/core.rs b/tools/didc-js/wasm-package/src/core.rs index 37faecae3..d8d3461e4 100644 --- a/tools/didc-js/wasm-package/src/core.rs +++ b/tools/didc-js/wasm-package/src/core.rs @@ -1,3 +1,5 @@ +use core::slice; + use crate::{types::EncodeType, validation::Validate}; use candid::{ types::{Type, TypeInner}, @@ -63,7 +65,7 @@ pub fn encode(args: EncodeArgs) -> Result { })?; idl_args - .to_bytes_with_types(&idl.env, &[type_def.clone()]) + .to_bytes_with_types(&idl.env, slice::from_ref(type_def)) .map_err(|e| LibraryError::IdlArgsToBytesFailed { reason: format!("Could not encode input to bytes {}", e), })? From 372155799d78aa45bc800b515ca5a3b4b179abe0 Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 11 Aug 2025 09:40:38 +0200 Subject: [PATCH 07/11] refactor: pretty printing combinator for lists/tuples (#668) Gets rid of the dodgy `is_empty` function that performed full traversals of documents to tell if they were empty. --- CHANGELOG.md | 13 +- rust/candid/src/pretty/candid.rs | 52 +++--- rust/candid/src/pretty/utils.rs | 176 ++++++++++++------ rust/candid_parser/src/bindings/javascript.rs | 60 ++---- rust/candid_parser/src/bindings/motoko.rs | 13 +- rust/candid_parser/src/bindings/rust.rs | 17 +- rust/candid_parser/src/bindings/typescript.rs | 26 ++- rust/candid_parser/src/syntax/pretty.rs | 17 +- rust/candid_parser/tests/assets/ok/class.rs | 2 +- rust/candid_parser/tests/assets/ok/empty.rs | 4 +- rust/candid_parser/tests/assets/ok/example.rs | 10 +- .../candid_parser/tests/assets/ok/fieldnat.rs | 6 +- rust/candid_parser/tests/assets/ok/keyword.rs | 4 +- rust/candid_parser/tests/assets/ok/service.rs | 2 +- 14 files changed, 222 insertions(+), 180 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c253d1e2..fed4ac12b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ + The `args` field of the `candid::types::internal::Function` struct now is a `Vec` instead of `Vec`, to preserve argument names. + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. +* [BREAKING]: Removed the `candid::pretty::concat` function + + `candid::pretty::enclose` and `candid::pretty:enclose_space` don't collapse the separators on empty documents anymore + * Non-breaking changes: + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. @@ -409,7 +412,7 @@ The source code of this tool has been removed, as it was deprecated in [PR#405]( * Bump ic-types to 0.3 * `candid::utils::service_compatible` to check for upgrade compatibility of two service types -## 2021-12-20 +## 2021-12-20 ### Rust (0.7.9) @@ -435,7 +438,7 @@ The source code of this tool has been removed, as it was deprecated in [PR#405]( ### Rust (0.7.5 -- 0.7.7) -* Support import when parsing did files with `check_file` function +* Support import when parsing did files with `check_file` function * Fix TypeScript binding for reference types ### Candid UI @@ -454,7 +457,7 @@ The source code of this tool has been removed, as it was deprecated in [PR#405]( * Add `#[candid_path("path_to_candid")]` helper attribute to the candid derive macro * Update `ic-types` to 0.2.0 -## 2021-06-03 +## 2021-06-03 ### Spec @@ -494,7 +497,7 @@ The source code of this tool has been removed, as it was deprecated in [PR#405]( * Fix TypeScript binding for tuple * Rust support for Func and Service value -## 2021-03-17 +## 2021-03-17 ### Rust (0.6.18) @@ -645,7 +648,7 @@ The source code of this tool has been removed, as it was deprecated in [PR#405]( * No longer requires the shortest LEB128 number in deserialization [#79](https://github.com/dfinity/candid/pull/79) -### Rust +### Rust * Parser improvements: + Floats in fractional number, no e-notation yet diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 48a2b4770..1d4fe42bd 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -107,8 +107,8 @@ pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc<'_> { Record(ref fs) => { let t = Type(ty.clone().into()); if t.is_tuple() { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ";"); - kwd("record").append(enclose_space("{", tuple, "}")) + let fs = fs.iter().map(|f| pp_ty(&f.ty)); + kwd("record").append(sep_enclose_space(fs, ";", "{", "}")) } else { kwd("record").append(pp_fields(fs, false)) } @@ -151,8 +151,7 @@ pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc<'_> { } fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { - let fields = fs.iter().map(|f| pp_field(f, is_variant)); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fs.iter().map(|f| pp_field(f, is_variant)), ";", "{", "}") } pub fn pp_function(func: &Function) -> RcDoc<'_> { @@ -176,16 +175,14 @@ pub fn pp_named_args(args: &[ArgType]) -> RcDoc<'_> { pp_ty(&arg.typ) } }); - let doc = concat(args, ","); - enclose("(", doc, ")") + sep_enclose(args, ",", "(", ")") } /// Pretty-prints arguments in the form of `(type1, type2)`. /// /// To print named arguments, use [`pp_named_args`] instead. pub fn pp_args(args: &[Type]) -> RcDoc<'_> { - let doc = concat(args.iter().map(pp_ty), ","); - enclose("(", doc, ")") + sep_enclose(args.iter().map(pp_ty), ",", "(", ")") } /// Pretty-prints return types in the form of `(type1, type2)`. @@ -205,22 +202,19 @@ pub fn pp_modes(modes: &[FuncMode]) -> RcDoc<'_> { } fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> RcDoc<'a> { - let doc = concat( - serv.iter().map(|(id, func)| { - let doc = docs - .and_then(|docs| docs.lookup_service_method(id)) - .map(|docs| pp_docs(docs)) - .unwrap_or(RcDoc::nil()); - let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => pp_function(f), - TypeInner::Var(_) => pp_ty(func), - _ => unreachable!(), - }; - doc.append(pp_text(id)).append(kwd(" :")).append(func_doc) - }), - ";", - ); - enclose_space("{", doc, "}") + let methods = serv.iter().map(|(id, func)| { + let doc = docs + .and_then(|docs| docs.lookup_service_method(id)) + .map(|docs| pp_docs(docs)) + .unwrap_or(RcDoc::nil()); + let func_doc = match func.as_ref() { + TypeInner::Func(ref f) => pp_function(f), + TypeInner::Var(_) => pp_ty(func), + _ => unreachable!(), + }; + doc.append(pp_text(id)).append(kwd(" :")).append(func_doc) + }); + sep_enclose_space(methods, ";", "{", "}") } fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { @@ -533,8 +527,8 @@ pub mod value { } fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc<'_> { - let fs = concat(fields.iter().map(|f| pp_field(depth, f, false)), ";"); - enclose_space("{", fs, "}") + let fs = fields.iter().map(|f| pp_field(depth, f, false)); + sep_enclose_space(fs, ";", "{", "}") } pub fn pp_char(v: u8) -> String { @@ -561,13 +555,13 @@ pub mod value { RcDoc::as_string(format!("{v:?}")) } else { let values = vs.iter().map(|v| pp_value(depth - 1, v)); - kwd("vec").append(enclose_space("{", concat(values, ";"), "}")) + kwd("vec").append(sep_enclose_space(values, ";", "{", "}")) } } Record(fields) => { if is_tuple(v) { let fields = fields.iter().map(|f| pp_value(depth - 1, &f.val)); - kwd("record").append(enclose_space("{", concat(fields, ";"), "}")) + kwd("record").append(sep_enclose_space(fields, ";", "{", "}")) } else { kwd("record").append(pp_fields(depth, fields)) } @@ -584,6 +578,6 @@ pub mod value { .args .iter() .map(|v| pp_value(MAX_ELEMENTS_FOR_PRETTY_PRINT, v)); - enclose("(", concat(args, ","), ")") + sep_enclose(args, ",", "(", ")") } } diff --git a/rust/candid/src/pretty/utils.rs b/rust/candid/src/pretty/utils.rs index 9e1a911ef..3b2844f41 100644 --- a/rust/candid/src/pretty/utils.rs +++ b/rust/candid/src/pretty/utils.rs @@ -1,45 +1,26 @@ -use pretty::RcDoc; +use pretty::{RcAllocator, RcDoc}; pub const INDENT_SPACE: isize = 2; pub const LINE_WIDTH: usize = 80; -fn is_empty(doc: &RcDoc) -> bool { - use pretty::Doc::*; - match &**doc { - Nil => true, - FlatAlt(t1, t2) => is_empty(t2) || is_empty(t1), - Union(t1, t2) => is_empty(t1) && is_empty(t2), - Group(t) | Nest(_, t) | Annotated((), t) => is_empty(t), - _ => false, - } -} - pub fn enclose<'a>(left: &'a str, doc: RcDoc<'a>, right: &'a str) -> RcDoc<'a> { - if is_empty(&doc) { - RcDoc::text(left).append(right) - } else { - RcDoc::text(left) - .append(RcDoc::line_()) - .append(doc) - .nest(INDENT_SPACE) - .append(RcDoc::line_()) - .append(right) - .group() - } + RcDoc::text(left) + .append(RcDoc::line_()) + .append(doc) + .nest(INDENT_SPACE) + .append(RcDoc::line_()) + .append(right) + .group() } pub fn enclose_space<'a>(left: &'a str, doc: RcDoc<'a>, right: &'a str) -> RcDoc<'a> { - if is_empty(&doc) { - RcDoc::text(left).append(right) - } else { - RcDoc::text(left) - .append(RcDoc::line()) - .append(doc) - .nest(INDENT_SPACE) - .append(RcDoc::line()) - .append(right) - .group() - } + RcDoc::text(left) + .append(RcDoc::line()) + .append(doc) + .nest(INDENT_SPACE) + .append(RcDoc::line()) + .append(right) + .group() } /// Intersperse the separator between each item in `docs`. @@ -50,20 +31,6 @@ where RcDoc::intersperse(docs, RcDoc::text(sep).append(RcDoc::line())) } -/// Append the separator to each item in `docs`. If it is displayed in a single line, omit the last separator. -pub fn concat<'a, D>(docs: D, sep: &'a str) -> RcDoc<'a> -where - D: Iterator>, -{ - let mut docs = docs.peekable(); - if docs.peek().is_none() { - return RcDoc::nil(); - } - let singleline = RcDoc::intersperse(docs, RcDoc::text(sep).append(RcDoc::line())); - let multiline = singleline.clone().append(sep); - multiline.flat_alt(singleline) -} - pub fn lines<'a, D>(docs: D) -> RcDoc<'a> where D: Iterator>, @@ -90,15 +57,120 @@ pub fn quote_ident(id: &str) -> RcDoc<'_> { .append(RcDoc::space()) } +/// Separate each item in `docs` with the separator `sep`, and enclose the result in `open` and `close`. +/// When placed on multiple lines, the last element gets a trailing separator. +pub fn sep_enclose<'a, D, S, O, C>(docs: D, sep: S, open: O, close: C) -> RcDoc<'a> +where + D: IntoIterator>, + S: pretty::Pretty<'a, RcAllocator>, + O: pretty::Pretty<'a, RcAllocator>, + C: pretty::Pretty<'a, RcAllocator>, +{ + let sep = sep.pretty(&RcAllocator); + let elems = RcDoc::intersperse(docs, sep.clone().append(RcDoc::line())); + open.pretty(&RcAllocator) + .into_doc() + .append(RcDoc::line_()) + .append(elems) + .append(sep.flat_alt(RcDoc::nil())) + .nest(INDENT_SPACE) + .append(RcDoc::line_()) + .append(close.pretty(&RcAllocator)) + .group() +} + +/// Like `sep_enclose`, but inserts a space between the opening delimiter and the first element, +/// and between the last element and the closing delimiter when placed on a single line. +pub fn sep_enclose_space<'a, D, S, O, C>(docs: D, sep: S, open: O, close: C) -> RcDoc<'a> +where + D: IntoIterator>, + S: pretty::Pretty<'a, RcAllocator>, + O: pretty::Pretty<'a, RcAllocator>, + C: pretty::Pretty<'a, RcAllocator>, +{ + let mut docs = docs.into_iter().peekable(); + if docs.peek().is_none() { + return open.pretty(&RcAllocator).append(close).into_doc(); + } + let open = open + .pretty(&RcAllocator) + .append(RcDoc::nil().flat_alt(RcDoc::space())); + let close = RcDoc::nil().flat_alt(RcDoc::space()).append(close); + sep_enclose(docs, sep, open, close) +} + #[cfg(test)] -mod test { +mod tests { use super::*; #[test] - fn concat_empty() { - let t = concat(vec![].into_iter(), ",") + fn enclose_empty() { + let t = sep_enclose(vec![], ",", "(", ")") .pretty(LINE_WIDTH) .to_string(); - assert_eq!(t, ""); + assert_eq!(t, "()"); + } + + #[test] + fn enclose_single_line() { + let printed = sep_enclose(vec![str("a"), str("b")], ",", "(", ")") + .pretty(LINE_WIDTH) + .to_string(); + assert_eq!(printed, "(a, b)"); + } + + #[test] + fn enclose_multiline() { + let docs: Vec = vec![ + str("Very long line to make sure we get a multiline document"), + str("Very long line to make sure we get a multiline document"), + ]; + let printed = sep_enclose(docs, ",", "(", ")").pretty(20).to_string(); + assert_eq!( + printed, + " +( + Very long line to make sure we get a multiline document, + Very long line to make sure we get a multiline document, +) +" + .trim() + ); + } + + #[test] + fn enclose_empty_space() { + let t = sep_enclose_space(vec![], ",", "(", ")") + .pretty(LINE_WIDTH) + .to_string(); + assert_eq!(t, "()"); + } + + #[test] + fn enclose_single_line_space() { + let printed = sep_enclose_space(vec![str("a"), str("b")], ",", "(", ")") + .pretty(LINE_WIDTH) + .to_string(); + assert_eq!(printed, "( a, b )"); + } + + #[test] + fn enclose_multiline_space() { + let docs: Vec = vec![ + str("Very long line to make sure we get a multiline document"), + str("Very long line to make sure we get a multiline document"), + ]; + let printed = sep_enclose_space(docs, ",", "(", ")") + .pretty(20) + .to_string(); + assert_eq!( + printed, + " +( + Very long line to make sure we get a multiline document, + Very long line to make sure we get a multiline document, +)" + .trim() + ); } } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 78fad89fd..92b9da626 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -125,8 +125,8 @@ fn pp_ty(ty: &Type) -> RcDoc<'_> { Vec(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), Record(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ","); - str("IDL.Tuple").append(enclose("(", tuple, ")")) + let fs = fs.iter().map(|f| pp_ty(&f.ty)); + str("IDL.Tuple").append(sep_enclose(fs, ",", "(", ")")) } else { str("IDL.Record").append(pp_fields(fs)) } @@ -156,45 +156,37 @@ fn pp_field(field: &Field) -> RcDoc<'_> { } fn pp_fields(fs: &[Field]) -> RcDoc<'_> { - let fields = concat(fs.iter().map(pp_field), ","); - enclose_space("({", fields, "})") + sep_enclose_space(fs.iter().map(pp_field), ",", "({", "})") } fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); - let doc = concat([args, rets, modes].into_iter(), ","); - enclose("(", doc, ")").nest(INDENT_SPACE) + sep_enclose([args, rets, modes], ",", "(", ")").nest(INDENT_SPACE) } fn pp_args(args: &[ArgType]) -> RcDoc<'_> { - let doc = concat(args.iter().map(|arg| pp_ty(&arg.typ)), ","); - enclose("[", doc, "]") + let args = args.iter().map(|arg| pp_ty(&arg.typ)); + sep_enclose(args, ",", "[", "]") } fn pp_rets(args: &[Type]) -> RcDoc<'_> { - let doc = concat(args.iter().map(pp_ty), ","); - enclose("[", doc, "]") + sep_enclose(args.iter().map(pp_ty), ",", "[", "]") } fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc<'_> { - let doc = concat( - modes - .iter() - .map(|m| str("'").append(pp_mode(m)).append("'")), - ",", - ); - enclose("[", doc, "]") + let ms = modes + .iter() + .map(|m| str("'").append(pp_mode(m)).append("'")); + sep_enclose(ms, ",", "[", "]") } fn pp_service(serv: &[(String, Type)]) -> RcDoc<'_> { - let doc = concat( - serv.iter() - .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))), - ",", - ); - enclose_space("({", doc, "})") + let ms = serv + .iter() + .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))); + sep_enclose_space(ms, ",", "({", "})") } fn pp_defs<'a>( @@ -313,10 +305,6 @@ pub mod value { .append(pp_value(&field.val)) } - fn pp_fields(fields: &[IDLField]) -> RcDoc<'_> { - concat(fields.iter().map(pp_field), ",") - } - pub fn pp_value(v: &IDLValue) -> RcDoc<'_> { use IDLValue::*; match v { @@ -342,20 +330,13 @@ pub mod value { Text(s) => RcDoc::text(format!("'{}'", s.escape_debug())), None => RcDoc::text("[]"), Opt(v) => enclose_space("[", pp_value(v), "]"), - Blob(blob) => { - let body = concat(blob.iter().map(RcDoc::as_string), ","); - enclose_space("[", body, "]") - } - Vec(vs) => { - let body = concat(vs.iter().map(pp_value), ","); - enclose_space("[", body, "]") - } + Blob(blob) => sep_enclose_space(blob.iter().map(RcDoc::as_string), ",", "[", "]"), + Vec(vs) => sep_enclose_space(vs.iter().map(pp_value), ",", "[", "]"), Record(fields) => { if is_tuple(v) { - let tuple = concat(fields.iter().map(|f| pp_value(&f.val)), ","); - enclose_space("[", tuple, "]") + sep_enclose_space(fields.iter().map(|f| pp_value(&f.val)), ",", "[", "]") } else { - enclose_space("{", pp_fields(fields), "}") + sep_enclose_space(fields.iter().map(pp_field), ",", "{", "}") } } Variant(v) => enclose_space("{", pp_field(&v.0), "}"), @@ -363,7 +344,6 @@ pub mod value { } pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> { - let body = concat(args.args.iter().map(pp_value), ","); - enclose("[", body, "]") + sep_enclose(args.args.iter().map(pp_value), ",", "[", "]") } } diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 46fbe098f..49a5a6fa2 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -207,7 +207,7 @@ fn pp_args(args: &[ArgType]) -> RcDoc<'_> { pp_ty(&arg.typ) } }); - enclose("(", concat(args, ","), ")") + sep_enclose(args, ",", "(", ")") } } } @@ -221,7 +221,7 @@ fn pp_rets(args: &[Type]) -> RcDoc<'_> { pp_ty(ty) } } - _ => enclose("(", concat(args.iter().map(pp_ty), ","), ")"), + _ => sep_enclose(args.iter().map(pp_ty), ",", "(", ")"), } } @@ -239,12 +239,11 @@ fn pp_service<'a>(serv: &'a [(String, Type)], syntax: Option<&'a [syntax::Bindin .append(" : ") .append(pp_ty_rich(func, syntax_field_ty)) }); - kwd("actor").append(enclose_space("{", concat(methods, ";"), "}")) + kwd("actor").append(sep_enclose_space(methods, ";", "{", "}")) } fn pp_tuple<'a>(fields: &'a [Field]) -> RcDoc<'a> { - let tuple = concat(fields.iter().map(|f| pp_ty(&f.ty)), ","); - enclose("(", tuple, ")") + sep_enclose(fields.iter().map(|f| pp_ty(&f.ty)), ",", "(", ")") } fn pp_vec<'a>(inner: &'a Type, syntax: Option<&'a IDLType>) -> RcDoc<'a> { @@ -280,7 +279,7 @@ fn pp_record<'a>(fields: &'a [Field], syntax: Option<&'a [syntax::TypeField]>) - .append(" : ") .append(pp_ty_rich(&field.ty, syntax_field)) }); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fields, ";", "{", "}") } fn pp_variant<'a>(fields: &'a [Field], syntax: Option<&'a [syntax::TypeField]>) -> RcDoc<'a> { @@ -297,7 +296,7 @@ fn pp_variant<'a>(fields: &'a [Field], syntax: Option<&'a [syntax::TypeField]>) doc } }); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fields, ";", "{", "}") } fn pp_class<'a>((args, ty): (&'a [ArgType], &'a Type), syntax: Option<&'a IDLType>) -> RcDoc<'a> { diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index f789f21c8..88e609099 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -394,11 +394,11 @@ fn test_{test_name}() {{ } else { RcDoc::nil() }; - let res = vis.append(self.pp_ty(&f.ty, is_ref)).append(","); + let res = vis.append(self.pp_ty(&f.ty, is_ref)); self.state.pop_state(old, StateElem::Label(&lab)); res }); - enclose("(", RcDoc::concat(tuple), ")") + sep_enclose(tuple, ",", "(", ")") } fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool, is_ref: bool) -> RcDoc<'b> { @@ -435,8 +435,7 @@ fn test_{test_name}() {{ docs.append(self.pp_record_field(f, need_vis, is_ref)) }) .collect(); - let fields = concat(fields.into_iter(), ","); - enclose_space("{", fields, "}") + sep_enclose_space(fields, ",", "{", "}") }; if let Some(old) = old { self.state.pop_state(old, StateElem::TypeStr("record")); @@ -499,7 +498,7 @@ fn test_{test_name}() {{ let (docs, syntax_field) = find_field(syntax, &f.id); docs.append(self.pp_variant_field(f, syntax_field)) }); - let res = enclose_space("{", concat(fields, ","), "}"); + let res = sep_enclose_space(fields, ",", "{", "}"); self.state.pop_state(old, StateElem::TypeStr("variant")); res } @@ -611,14 +610,14 @@ fn test_{test_name}() {{ lines(res.into_iter()) } fn pp_args<'b>(&mut self, args: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { - let doc = args.iter().enumerate().map(|(i, t)| { + let args = args.iter().enumerate().map(|(i, t)| { let lab = t.name.clone().unwrap_or_else(|| format!("{prefix}{i}")); let old = self.state.push_state(&StateElem::Label(&lab)); let res = self.pp_ty(&t.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }); - enclose("(", concat(doc, ","), ")") + sep_enclose(args, ",", "(", ")") } fn pp_rets<'b>(&mut self, rets: &'b [Type], prefix: &'b str) -> RcDoc<'b> { let tys = rets.iter().enumerate().map(|(i, t)| { @@ -628,7 +627,7 @@ fn test_{test_name}() {{ self.state.pop_state(old, StateElem::Label(&lab)); res }); - enclose("(", concat(tys.into_iter(), ","), ")") + sep_enclose(tys, ",", "(", ")") } fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { let lab = StateElem::TypeStr("func"); @@ -659,7 +658,7 @@ fn test_{test_name}() {{ .append(kwd("\" :")) .append(func_doc) }); - let res = enclose_space("{", concat(methods, ";"), "}"); + let res = sep_enclose_space(methods, ";", "{", "}"); self.state.pop_state(old, lab); res } diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 3f8aaf094..e7191f8d9 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -161,17 +161,14 @@ fn pp_record<'a>( is_ref: bool, ) -> RcDoc<'a> { if is_tuple_fields(fields) { - let tuple = concat(fields.iter().map(|f| pp_ty(env, &f.ty, is_ref)), ","); - enclose("[", tuple, "]") + let fs = fields.iter().map(|f| pp_ty(env, &f.ty, is_ref)); + sep_enclose(fs, ",", "[", "]") } else { - let fields = concat( - fields.iter().map(|f| { - let (docs, syntax_field) = find_field(syntax, &f.id); - docs.append(pp_field(env, f, syntax_field, is_ref)) - }), - ",", - ); - enclose_space("{", fields, "}") + let fields = fields.iter().map(|f| { + let (docs, syntax_field) = find_field(syntax, &f.id); + docs.append(pp_field(env, f, syntax_field, is_ref)) + }); + sep_enclose_space(fields, ",", "{", "}") } } @@ -207,13 +204,14 @@ fn pp_opt<'a>( fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { let args = func.args.iter().map(|arg| pp_ty(env, &arg.typ, true)); - let args = enclose("[", concat(args, ","), "]"); + let args = sep_enclose(args, ",", "[", "]"); let rets = match func.rets.len() { 0 => str("undefined"), 1 => pp_ty(env, &func.rets[0], true), - _ => enclose( + _ => sep_enclose( + func.rets.iter().map(|ty| pp_ty(env, ty, true)), + ",", "[", - concat(func.rets.iter().map(|ty| pp_ty(env, ty, true)), ","), "]", ), }; @@ -243,7 +241,7 @@ fn pp_service<'a>( }; docs.append(quote_ident(id)).append(kwd(":")).append(func) }); - enclose_space("{", concat(methods, ","), "}") + sep_enclose_space(methods, ",", "{", "}") } fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { diff --git a/rust/candid_parser/src/syntax/pretty.rs b/rust/candid_parser/src/syntax/pretty.rs index 98b898e61..7480d336f 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -3,7 +3,7 @@ use pretty::RcDoc; use crate::{ pretty::{ candid::{pp_docs, pp_label_raw, pp_modes, pp_text}, - utils::{concat, enclose, enclose_space, ident, kwd, lines, str, INDENT_SPACE, LINE_WIDTH}, + utils::{ident, kwd, lines, sep_enclose, sep_enclose_space, str, INDENT_SPACE, LINE_WIDTH}, }, syntax::{ Binding, FuncType, IDLActorType, IDLArgType, IDLMergedProg, IDLType, PrimType, TypeField, @@ -54,7 +54,7 @@ fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc<'_> { fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc<'_> { let fields = fs.iter().map(|f| pp_field(f, is_variant)); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fields, ";", "{", "}") } fn pp_opt(ty: &IDLType) -> RcDoc<'_> { @@ -71,8 +71,8 @@ fn pp_vec(ty: &IDLType) -> RcDoc<'_> { fn pp_record(fs: &[TypeField], is_tuple: bool) -> RcDoc<'_> { if is_tuple { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ";"); - kwd("record").append(enclose_space("{", tuple, "}")) + let fs = fs.iter().map(|f| pp_ty(&f.typ)); + kwd("record").append(sep_enclose_space(fs, ";", "{", "}")) } else { kwd("record").append(pp_fields(fs, false)) } @@ -104,13 +104,11 @@ fn pp_args(args: &[IDLArgType]) -> RcDoc<'_> { pp_ty(&arg.typ) } }); - let doc = concat(args, ","); - enclose("(", doc, ")") + sep_enclose(args, ",", "(", ")") } fn pp_rets(rets: &[IDLType]) -> RcDoc<'_> { - let doc = concat(rets.iter().map(pp_ty), ","); - enclose("(", doc, ")") + sep_enclose(rets.iter().map(pp_ty), ",", "(", ")") } fn pp_service(methods: &[Binding]) -> RcDoc<'_> { @@ -129,8 +127,7 @@ fn pp_service_methods(methods: &[Binding]) -> RcDoc<'_> { .append(kwd(" :")) .append(func_doc) }); - let doc = concat(methods, ";"); - enclose_space("{", doc, "}") + sep_enclose_space(methods, ";", "{", "}") } fn pp_class<'a>(args: &'a [IDLArgType], t: &'a IDLType) -> RcDoc<'a> { diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index 44c590258..f7f94db2d 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -4,7 +4,7 @@ use candid::{self, CandidType, Deserialize, Principal}; #[derive(CandidType, Deserialize)] -pub struct List(pub Option<(candid::Int,Box,)>); +pub struct List(pub Option<(candid::Int, Box)>); #[derive(CandidType, Deserialize)] pub struct Profile { pub age: u8, pub name: String } diff --git a/rust/candid_parser/tests/assets/ok/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index 899477cc8..81d1ed30c 100644 --- a/rust/candid_parser/tests/assets/ok/empty.rs +++ b/rust/candid_parser/tests/assets/ok/empty.rs @@ -9,7 +9,7 @@ pub struct FArg {} #[derive(CandidType, Deserialize)] pub enum FRet {} #[derive(CandidType, Deserialize)] -pub struct T (pub Box,); +pub struct T (pub Box); #[derive(CandidType, Deserialize)] pub enum GRet { #[serde(rename="a")] A(Box) } #[derive(CandidType, Deserialize)] @@ -23,7 +23,7 @@ impl Service { pub async fn g(&self, arg0: &T) -> Result<(GRet,)> { ic_cdk::call(self.0, "g", (arg0,)).await } - pub async fn h(&self, arg0: &(T,candid::Empty,)) -> Result<(HRet,)> { + pub async fn h(&self, arg0: &(T, candid::Empty)) -> Result<(HRet,)> { ic_cdk::call(self.0, "h", (arg0,)).await } } diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index 131b5f62a..4653172d8 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -44,7 +44,7 @@ pub(crate) struct Nested { pub(crate) _0_: u128, pub(crate) _1_: u128, /// Doc comment for nested record - pub(crate) _2_: (u128,candid::Int,), + pub(crate) _2_: (u128, candid::Int), pub(crate) _3_: Nested3, pub(crate) _40_: u128, pub(crate) _41_: Nested41, @@ -60,7 +60,7 @@ candid::define_service!(pub(crate) Broker : { #[derive(CandidType, Deserialize, Debug)] pub(crate) struct NestedResErrOk { pub(crate) content: String } pub(crate) type NestedRes = std::result::Result< - my::Result<(), ()>, another::Result + my::Result<(), ()>, another::Result >; #[derive(CandidType, Deserialize, Debug)] pub(crate) enum HArg1 { A(u128), B(Option) } @@ -76,13 +76,13 @@ pub(crate) struct ResErr { pub(crate) error: String, } /// Doc comment for res type -pub(crate) type Res = std::result::Result<(candid::Int,u128,), ResErr>; +pub(crate) type Res = std::result::Result<(candid::Int, u128), ResErr>; candid::define_function!(pub(crate) F : (MyList, FArg1) -> ( Option, Res, )); #[derive(CandidType, Deserialize, Debug)] -pub(crate) struct B (pub(crate) candid::Int,pub(crate) u128,); +pub(crate) struct B (pub(crate) candid::Int, pub(crate) u128); #[derive(CandidType, Deserialize, Debug)] pub(crate) enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(B) } #[derive(CandidType, Deserialize, Debug)] @@ -167,7 +167,7 @@ impl Service { pub async fn x(&self, arg0: &A, arg1: &B) -> Result<(Option,Option,std::result::Result,)> { ic_cdk::call(self.0, "x", (arg0,arg1,)).await } - pub async fn y(&self, arg0: &NestedRecords) -> Result<((NestedRecords,MyVariant,),)> { + pub async fn y(&self, arg0: &NestedRecords) -> Result<((NestedRecords, MyVariant),)> { ic_cdk::call(self.0, "y", (arg0,)).await } pub async fn f(&self, server: &S) -> Result<()> { diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index 43c063fd1..55a926045 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -17,7 +17,7 @@ pub struct BazArg { #[derive(CandidType, Deserialize)] pub struct BazRet {} #[derive(CandidType, Deserialize)] -pub struct Tuple (pub String,pub String,); +pub struct Tuple (pub String, pub String); #[derive(CandidType, Deserialize)] pub struct NonTuple { pub _1_: String, pub _2_: String } #[derive(CandidType, Deserialize)] @@ -35,7 +35,7 @@ impl Service { pub async fn bar(&self, arg0: &BarArg) -> Result<(BarRet,)> { ic_cdk::call(self.0, "bar", (arg0,)).await } - pub async fn bas(&self, arg0: &(candid::Int,candid::Int,)) -> Result<((String,candid::Nat,),)> { + pub async fn bas(&self, arg0: &(candid::Int, candid::Int)) -> Result<((String, candid::Nat),)> { ic_cdk::call(self.0, "bas", (arg0,)).await } pub async fn baz(&self, arg0: &BazArg) -> Result<(BazRet,)> { @@ -44,7 +44,7 @@ impl Service { pub async fn bba(&self, arg0: &Tuple) -> Result<(NonTuple,)> { ic_cdk::call(self.0, "bba", (arg0,)).await } - pub async fn bib(&self, arg0: &(candid::Int,)) -> Result<(BibRet,)> { + pub async fn bib(&self, arg0: &(candid::Int)) -> Result<(BibRet,)> { ic_cdk::call(self.0, "bib", (arg0,)).await } pub async fn foo(&self, arg0: &FooArg) -> Result<(FooRet,)> { diff --git a/rust/candid_parser/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index d25756994..313eb71c4 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.rs +++ b/rust/candid_parser/tests/assets/ok/keyword.rs @@ -51,7 +51,7 @@ impl Service { pub async fn field(&self, arg0: &FieldArg) -> Result<(FieldRet,)> { ic_cdk::call(self.0, "field", (arg0,)).await } - pub async fn fieldnat(&self, arg0: &FieldnatArg) -> Result<((candid::Int,),)> { + pub async fn fieldnat(&self, arg0: &FieldnatArg) -> Result<((candid::Int),)> { ic_cdk::call(self.0, "fieldnat", (arg0,)).await } pub async fn oneway(&self, arg0: &u8) -> Result<()> { @@ -69,7 +69,7 @@ impl Service { pub async fn service(&self, server: &Return) -> Result<()> { ic_cdk::call(self.0, "service", (server,)).await } - pub async fn tuple(&self, arg0: &(candid::Int,serde_bytes::ByteBuf,String,)) -> Result<((candid::Int,u8,),)> { + pub async fn tuple(&self, arg0: &(candid::Int, serde_bytes::ByteBuf, String)) -> Result<((candid::Int, u8),)> { ic_cdk::call(self.0, "tuple", (arg0,)).await } pub async fn variant(&self, arg0: &VariantArg) -> Result<()> { diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index cbcf60cd7..692b444b7 100644 --- a/rust/candid_parser/tests/assets/ok/service.rs +++ b/rust/candid_parser/tests/assets/ok/service.rs @@ -23,7 +23,7 @@ impl Service { pub async fn as_principal(&self) -> Result<(Service2,Func,)> { ic_cdk::call(self.0, "asPrincipal", ()).await } - pub async fn as_record(&self) -> Result<((Service2,Option,Func,),)> { + pub async fn as_record(&self) -> Result<((Service2, Option, Func),)> { ic_cdk::call(self.0, "asRecord", ()).await } pub async fn as_variant(&self) -> Result<(AsVariantRet,)> { From 5f419c57d79713ca3c6b23ee3140c2cedf239346 Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Mon, 18 Aug 2025 15:56:48 -0500 Subject: [PATCH 08/11] feat: JavaScript target IDL type object exports and factory function deprecations (#665) **Overview** The JavaScript target would be more useful (especially to Azle developers) if it exported the IDL type objects that it generates. We also no longer need the factory functions as of `3.0.0`, thus we will mark them deprecated and provide direct exports for that previous functionality. **Requirements** The JavaScript target should export all of the IDL type objects, including the generated `IDL.Service` and `init args`, without the need to use a factory function. **Considerations** There are no breaking changes besides some formatting of the generated JavaScript files. The `idflFactory` and `init` functions have been marked as deprecated but not removed. Developers can choose to import the `IDL` type objects now, but they are not forced to. This is mainly an ergonomic change with deprecations to prepare for a future with the best developer experience by default. --- CHANGELOG.md | 2 + rust/candid_parser/src/bindings/javascript.rs | 83 +++++-- rust/candid_parser/src/bindings/typescript.rs | 8 +- rust/candid_parser/tests/assets/ok/actor.d.ts | 4 +- rust/candid_parser/tests/assets/ok/actor.js | 20 ++ rust/candid_parser/tests/assets/ok/class.d.ts | 4 +- rust/candid_parser/tests/assets/ok/class.js | 16 ++ .../tests/assets/ok/comment.d.ts | 2 +- rust/candid_parser/tests/assets/ok/comment.js | 5 +- .../candid_parser/tests/assets/ok/cyclic.d.ts | 4 +- rust/candid_parser/tests/assets/ok/cyclic.js | 18 ++ rust/candid_parser/tests/assets/ok/empty.d.ts | 4 +- rust/candid_parser/tests/assets/ok/empty.js | 19 ++ .../candid_parser/tests/assets/ok/escape.d.ts | 4 +- rust/candid_parser/tests/assets/ok/escape.js | 17 ++ .../tests/assets/ok/example.d.ts | 4 +- rust/candid_parser/tests/assets/ok/example.js | 127 ++++++++++ .../tests/assets/ok/fieldnat.d.ts | 4 +- .../candid_parser/tests/assets/ok/fieldnat.js | 35 +++ .../tests/assets/ok/inline_methods.d.ts | 4 +- .../tests/assets/ok/inline_methods.js | 33 +++ .../tests/assets/ok/keyword.d.ts | 4 +- rust/candid_parser/tests/assets/ok/keyword.js | 68 +++++ .../tests/assets/ok/management.d.ts | 4 +- .../tests/assets/ok/management.js | 234 ++++++++++++++++++ .../tests/assets/ok/recursion.d.ts | 4 +- .../tests/assets/ok/recursion.js | 31 +++ .../tests/assets/ok/recursive_class.d.ts | 4 +- .../tests/assets/ok/recursive_class.js | 12 + .../tests/assets/ok/service.d.ts | 4 +- rust/candid_parser/tests/assets/ok/service.js | 27 ++ .../tests/assets/ok/unicode.d.ts | 4 +- rust/candid_parser/tests/assets/ok/unicode.js | 26 ++ 33 files changed, 801 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fed4ac12b..d9c3822e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * Non-breaking changes: + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. + + The `JavaScript` `didc` target now exports its generated IDL type objects. + + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function altneratives to `idlFactory` and `init`). ### candid_parser diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 92b9da626..ad750c9d1 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -193,25 +193,36 @@ fn pp_defs<'a>( env: &'a TypeEnv, def_list: &'a [&'a str], recs: &'a BTreeSet<&'a str>, + export: bool, ) -> RcDoc<'a> { - let recs_doc = lines( - recs.iter() - .map(|id| kwd("const").append(ident(id)).append(" = IDL.Rec();")), - ); - let defs = lines(def_list.iter().map(|&id| { + let export_prefix = if export { str("export ") } else { RcDoc::nil() }; + + let recs_doc = lines(recs.iter().map(|id| { + export_prefix + .clone() + .append(kwd("const")) + .append(ident(id)) + .append(" = IDL.Rec();") + })); + let mut defs = lines(def_list.iter().map(|&id| { let ty = env.find_type(&id.into()).unwrap(); if recs.contains(id) { ident(id) .append(".fill") .append(enclose("(", pp_ty(ty), ");")) } else { - kwd("const") + export_prefix + .clone() + .append(kwd("const")) .append(ident(id)) .append(" = ") .append(pp_ty(ty)) .append(";") } })); + if !def_list.is_empty() { + defs = defs.append(RcDoc::hardline()) + } recs_doc.append(defs) } @@ -230,39 +241,71 @@ fn pp_actor<'a>(ty: &'a Type, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { } } +fn pp_imports<'a>() -> RcDoc<'a> { + str("import { IDL } from '@dfinity/candid';") + .append(RcDoc::hardline()) + .append(RcDoc::hardline()) +} + pub fn compile(env: &TypeEnv, actor: &Option) -> String { match actor { None => { let def_list: Vec<_> = env.to_sorted_iter().map(|pair| pair.0.as_str()).collect(); let recs = infer_rec(env, &def_list).unwrap(); - let doc = pp_defs(env, &def_list, &recs); - doc.pretty(LINE_WIDTH).to_string() + let doc = pp_defs(env, &def_list, &recs, true); + let result = pp_imports().append(doc).pretty(LINE_WIDTH).to_string(); + + result } Some(actor) => { let def_list = chase_actor(env, actor).unwrap(); let recs = infer_rec(env, &def_list).unwrap(); - let defs = pp_defs(env, &def_list, &recs); let types = if let TypeInner::Class(ref args, _) = actor.as_ref() { args.iter().map(|arg| arg.typ.clone()).collect::>() } else { Vec::new() }; - let init = types.as_slice(); - let actor = kwd("return").append(pp_actor(actor, &recs)).append(";"); - let body = defs.append(actor); - let doc = str("export const idlFactory = ({ IDL }) => ") - .append(enclose_space("{", body, "};")); - // export init args - let init_defs = chase_types(env, init).unwrap(); + let init_types = types.as_slice(); + + let defs = pp_defs(env, &def_list, &recs, true); + let actor = pp_actor(actor, &recs); + + let idl_service = str("export const idlService = ") + .append(actor.clone()) + .append(";"); + + let idl_init_args = str("export const idlInitArgs = ") + .append(pp_rets(init_types)) + .append(";"); + + let idl_factory_return = kwd("return").append(actor).append(";"); + let idl_factory_body = pp_defs(env, &def_list, &recs, false).append(idl_factory_return); + let idl_factory_doc = str("export const idlFactory = ({ IDL }) => ") + .append(enclose_space("{", idl_factory_body, "};")); + + let init_defs = chase_types(env, init_types).unwrap(); let init_recs = infer_rec(env, &init_defs).unwrap(); - let init_defs_doc = pp_defs(env, &init_defs, &init_recs); - let init_doc = kwd("return").append(pp_rets(init)).append(";"); + let init_defs_doc = pp_defs(env, &init_defs, &init_recs, false); + let init_doc = kwd("return").append(pp_rets(init_types)).append(";"); let init_doc = init_defs_doc.append(init_doc); let init_doc = str("export const init = ({ IDL }) => ").append(enclose_space("{", init_doc, "};")); let init_doc = init_doc.pretty(LINE_WIDTH).to_string(); - let doc = doc.append(RcDoc::hardline()).append(init_doc); - doc.pretty(LINE_WIDTH).to_string() + + let result = pp_imports() + .append(defs) + .append(idl_service) + .append(RcDoc::hardline()) + .append(RcDoc::hardline()) + .append(idl_init_args) + .append(RcDoc::hardline()) + .append(RcDoc::hardline()) + .append(idl_factory_doc) + .append(RcDoc::hardline()) + .append(RcDoc::hardline()) + .append(init_doc); + + result.pretty(LINE_WIDTH).to_string() } } } diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index e7191f8d9..b53adee40 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -317,9 +317,9 @@ fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type, syntax: Option<&'a IDLType>) -> } pub fn compile(env: &TypeEnv, actor: &Option, prog: &IDLMergedProg) -> String { - let header = r#"import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; + let header = r#"import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; "#; let syntax_actor = prog.resolve_actor().ok().flatten(); let def_list: Vec<_> = env.to_sorted_iter().map(|pair| pair.0.as_str()).collect(); @@ -332,6 +332,10 @@ import type { IDL } from '@dfinity/candid'; .map(|s| pp_docs(s.docs.as_ref())) .unwrap_or(RcDoc::nil()); docs.append(pp_actor(env, actor, syntax_actor.as_ref().map(|s| &s.typ))) + .append(RcDoc::line()) + .append("export declare const idlService: IDL.ServiceClass;") + .append(RcDoc::line()) + .append("export declare const idlInitArgs: IDL.Type[];") .append(RcDoc::line()) .append("export declare const idlFactory: IDL.InterfaceFactory;") .append(RcDoc::line()) diff --git a/rust/candid_parser/tests/assets/ok/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts index 780b66b7c..fd1c863e8 100644 --- a/rust/candid_parser/tests/assets/ok/actor.d.ts +++ b/rust/candid_parser/tests/assets/ok/actor.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type f = ActorMethod<[number], number>; export type g = f; @@ -13,5 +13,7 @@ export interface _SERVICE { 'h2' : h, 'o' : ActorMethod<[o], o>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/actor.js b/rust/candid_parser/tests/assets/ok/actor.js index 9d59adae4..b6682ed29 100644 --- a/rust/candid_parser/tests/assets/ok/actor.js +++ b/rust/candid_parser/tests/assets/ok/actor.js @@ -1,9 +1,28 @@ +import { IDL } from '@dfinity/candid'; + +export const o = IDL.Rec(); +export const f = IDL.Func([IDL.Int8], [IDL.Int8], []); +export const h = IDL.Func([f], [f], []); +export const g = f; +o.fill(IDL.Opt(o)); + +export const idlService = IDL.Service({ + 'f' : IDL.Func([IDL.Nat], [h], []), + 'g' : f, + 'h' : g, + 'h2' : h, + 'o' : IDL.Func([o], [o], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const o = IDL.Rec(); const f = IDL.Func([IDL.Int8], [IDL.Int8], []); const h = IDL.Func([f], [f], []); const g = f; o.fill(IDL.Opt(o)); + return IDL.Service({ 'f' : IDL.Func([IDL.Nat], [h], []), 'g' : f, @@ -12,4 +31,5 @@ export const idlFactory = ({ IDL }) => { 'o' : IDL.Func([o], [o], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index acd196f00..f10f30bcf 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type List = [] | [[bigint, List]]; export interface Profile { 'age' : number, 'name' : string } @@ -14,5 +14,7 @@ export interface _SERVICE { 'get' : ActorMethod<[], List>, 'set' : ActorMethod<[List], List>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/class.js b/rust/candid_parser/tests/assets/ok/class.js index 5f052ee01..8ab176452 100644 --- a/rust/candid_parser/tests/assets/ok/class.js +++ b/rust/candid_parser/tests/assets/ok/class.js @@ -1,15 +1,31 @@ +import { IDL } from '@dfinity/candid'; + +export const List = IDL.Rec(); +List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List))); +export const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text }); + +export const idlService = IDL.Service({ + 'get' : IDL.Func([], [List], []), + 'set' : IDL.Func([List], [List], []), +}); + +export const idlInitArgs = [IDL.Int, List, Profile]; + export const idlFactory = ({ IDL }) => { const List = IDL.Rec(); List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List))); const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text }); + return IDL.Service({ 'get' : IDL.Func([], [List], []), 'set' : IDL.Func([List], [List], []), }); }; + export const init = ({ IDL }) => { const List = IDL.Rec(); List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List))); const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text }); + return [IDL.Int, List, Profile]; }; diff --git a/rust/candid_parser/tests/assets/ok/comment.d.ts b/rust/candid_parser/tests/assets/ok/comment.d.ts index ff7d3a185..f9d1f7b58 100644 --- a/rust/candid_parser/tests/assets/ok/comment.d.ts +++ b/rust/candid_parser/tests/assets/ok/comment.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; /** * line comment diff --git a/rust/candid_parser/tests/assets/ok/comment.js b/rust/candid_parser/tests/assets/ok/comment.js index 1f63d4782..6a3800d13 100644 --- a/rust/candid_parser/tests/assets/ok/comment.js +++ b/rust/candid_parser/tests/assets/ok/comment.js @@ -1,2 +1,5 @@ -const id = IDL.Nat8; +import { IDL } from '@dfinity/candid'; + +export const id = IDL.Nat8; + diff --git a/rust/candid_parser/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts index d75b132a3..ba6ef8f36 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.d.ts +++ b/rust/candid_parser/tests/assets/ok/cyclic.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type A = [] | [B]; export type B = [] | [C]; @@ -9,5 +9,7 @@ export type X = Y; export type Y = Z; export type Z = A; export interface _SERVICE { 'f' : ActorMethod<[A, B, C, X, Y, Z], undefined> } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.js b/rust/candid_parser/tests/assets/ok/cyclic.js index 528eaa6f3..8a04059a4 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.js +++ b/rust/candid_parser/tests/assets/ok/cyclic.js @@ -1,3 +1,19 @@ +import { IDL } from '@dfinity/candid'; + +export const A = IDL.Rec(); +export const C = A; +export const B = IDL.Opt(C); +A.fill(IDL.Opt(B)); +export const Z = A; +export const Y = Z; +export const X = Y; + +export const idlService = IDL.Service({ + 'f' : IDL.Func([A, B, C, X, Y, Z], [], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const A = IDL.Rec(); const C = A; @@ -6,6 +22,8 @@ export const idlFactory = ({ IDL }) => { const Z = A; const Y = Z; const X = Y; + return IDL.Service({ 'f' : IDL.Func([A, B, C, X, Y, Z], [], []) }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/empty.d.ts b/rust/candid_parser/tests/assets/ok/empty.d.ts index e01c5afbf..2897b15fd 100644 --- a/rust/candid_parser/tests/assets/ok/empty.d.ts +++ b/rust/candid_parser/tests/assets/ok/empty.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type T = [T]; export interface _SERVICE { @@ -8,5 +8,7 @@ export interface _SERVICE { 'g' : ActorMethod<[T], { 'a' : T }>, 'h' : ActorMethod<[[T, never]], { 'a' : T } | { 'b' : {} }>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/empty.js b/rust/candid_parser/tests/assets/ok/empty.js index 74b91c7cc..a4d036997 100644 --- a/rust/candid_parser/tests/assets/ok/empty.js +++ b/rust/candid_parser/tests/assets/ok/empty.js @@ -1,6 +1,24 @@ +import { IDL } from '@dfinity/candid'; + +export const T = IDL.Rec(); +T.fill(IDL.Tuple(T)); + +export const idlService = IDL.Service({ + 'f' : IDL.Func([IDL.Record({})], [IDL.Variant({})], []), + 'g' : IDL.Func([T], [IDL.Variant({ 'a' : T })], []), + 'h' : IDL.Func( + [IDL.Tuple(T, IDL.Empty)], + [IDL.Variant({ 'a' : T, 'b' : IDL.Record({}) })], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const T = IDL.Rec(); T.fill(IDL.Tuple(T)); + return IDL.Service({ 'f' : IDL.Func([IDL.Record({})], [IDL.Variant({})], []), 'g' : IDL.Func([T], [IDL.Variant({ 'a' : T })], []), @@ -11,4 +29,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/escape.d.ts b/rust/candid_parser/tests/assets/ok/escape.d.ts index 54eaf207c..325019950 100644 --- a/rust/candid_parser/tests/assets/ok/escape.d.ts +++ b/rust/candid_parser/tests/assets/ok/escape.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export interface t { '\"' : bigint, @@ -9,5 +9,7 @@ export interface t { '\\\n\'\"' : bigint, } export interface _SERVICE { '\n\'\"\'\'\"\"\r\t' : ActorMethod<[t], undefined> } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/escape.js b/rust/candid_parser/tests/assets/ok/escape.js index b46d9cebf..68e3b466d 100644 --- a/rust/candid_parser/tests/assets/ok/escape.js +++ b/rust/candid_parser/tests/assets/ok/escape.js @@ -1,3 +1,18 @@ +import { IDL } from '@dfinity/candid'; + +export const t = IDL.Record({ + '\"' : IDL.Nat, + '\'' : IDL.Nat, + '\"\'' : IDL.Nat, + '\\\n\'\"' : IDL.Nat, +}); + +export const idlService = IDL.Service({ + '\n\'\"\'\'\"\"\r\t' : IDL.Func([t], [], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const t = IDL.Record({ '\"' : IDL.Nat, @@ -5,6 +20,8 @@ export const idlFactory = ({ IDL }) => { '\"\'' : IDL.Nat, '\\\n\'\"' : IDL.Nat, }); + return IDL.Service({ '\n\'\"\'\'\"\"\r\t' : IDL.Func([t], [], []) }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index dcdc5520b..f8c61ea71 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type A = B; export type B = [] | [A]; @@ -187,5 +187,7 @@ export interface _SERVICE { */ 'bbbbb' : ActorMethod<[b], undefined>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/example.js b/rust/candid_parser/tests/assets/ok/example.js index fe10e4cef..5a609879c 100644 --- a/rust/candid_parser/tests/assets/ok/example.js +++ b/rust/candid_parser/tests/assets/ok/example.js @@ -1,3 +1,128 @@ +import { IDL } from '@dfinity/candid'; + +export const B = IDL.Rec(); +export const List = IDL.Rec(); +export const list = IDL.Rec(); +export const stream = IDL.Rec(); +export const t = IDL.Rec(); +export const tree = IDL.Rec(); +export const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list }); +list.fill(IDL.Opt(node)); +export const my_type = IDL.Principal; +List.fill(IDL.Opt(IDL.Record({ 'head' : IDL.Int, 'tail' : List }))); +export const nested = IDL.Record({ + _0_ : IDL.Nat, + _1_ : IDL.Nat, + _2_ : IDL.Tuple(IDL.Nat, IDL.Int), + _3_ : IDL.Record({ _0_ : IDL.Nat, _42_ : IDL.Nat, _43_ : IDL.Nat8 }), + _40_ : IDL.Nat, + _41_ : IDL.Variant({ + _42_ : IDL.Null, + 'A' : IDL.Null, + 'B' : IDL.Null, + 'C' : IDL.Null, + }), + _42_ : IDL.Nat, +}); +export const broker = IDL.Service({ + 'find' : IDL.Func( + [IDL.Text], + [ + IDL.Service({ + 'current' : IDL.Func([], [IDL.Nat32], []), + 'up' : IDL.Func([], [], []), + }), + ], + [], + ), +}); +export const nested_res = IDL.Variant({ + 'Ok' : IDL.Variant({ 'Ok' : IDL.Null, 'Err' : IDL.Null }), + 'Err' : IDL.Variant({ + 'Ok' : IDL.Record({ 'content' : IDL.Text }), + 'Err' : IDL.Tuple(IDL.Int), + }), +}); +export const res = IDL.Variant({ + 'Ok' : IDL.Tuple(IDL.Int, IDL.Nat), + 'Err' : IDL.Record({ 'error' : IDL.Text }), +}); +export const f = IDL.Func( + [List, IDL.Func([IDL.Int32], [IDL.Int64], [])], + [IDL.Opt(List), res], + [], + ); +export const b = IDL.Tuple(IDL.Int, IDL.Nat); +export const a = IDL.Variant({ 'a' : IDL.Null, 'b' : b }); +export const nested_records = IDL.Record({ + 'nested' : IDL.Opt(IDL.Record({ 'nested_field' : IDL.Text })), +}); +export const my_variant = IDL.Variant({ + 'a' : IDL.Record({ 'b' : IDL.Text }), + 'c' : IDL.Opt( + IDL.Record({ 'd' : IDL.Text, 'e' : IDL.Vec(IDL.Record({ 'f' : IDL.Nat })) }) + ), +}); +export const A = B; +B.fill(IDL.Opt(A)); +tree.fill( + IDL.Variant({ + 'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : tree, 'right' : tree }), + 'leaf' : IDL.Int, + }) +); +stream.fill( + IDL.Opt( + IDL.Record({ 'head' : IDL.Nat, 'next' : IDL.Func([], [stream], ['query']) }) + ) +); +export const s = IDL.Service({ + 'f' : t, + 'g' : IDL.Func([list], [B, tree, stream], []), +}); +t.fill(IDL.Func([s], [], [])); + +export const idlService = IDL.Service({ + 'f1' : IDL.Func([list, IDL.Vec(IDL.Nat8), IDL.Opt(IDL.Bool)], [], ['oneway']), + 'g1' : IDL.Func( + [my_type, List, IDL.Opt(List), nested], + [IDL.Int, broker, nested_res], + ['query'], + ), + 'h' : IDL.Func( + [ + IDL.Vec(IDL.Opt(IDL.Text)), + IDL.Variant({ 'A' : IDL.Nat, 'B' : IDL.Opt(IDL.Text) }), + IDL.Opt(List), + ], + [IDL.Record({ _42_ : IDL.Record({}), 'id' : IDL.Nat })], + [], + ), + 'i' : f, + 'x' : IDL.Func( + [a, b], + [ + IDL.Opt(a), + IDL.Opt(b), + IDL.Variant({ + 'Ok' : IDL.Record({ 'result' : IDL.Text }), + 'Err' : IDL.Variant({ 'a' : IDL.Null, 'b' : IDL.Null }), + }), + ], + ['composite_query'], + ), + 'y' : IDL.Func( + [nested_records], + [IDL.Tuple(nested_records, my_variant)], + ['query'], + ), + 'f' : t, + 'g' : IDL.Func([list], [B, tree, stream], []), + 'bbbbb' : IDL.Func([b], [], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const B = IDL.Rec(); const List = IDL.Rec(); @@ -86,6 +211,7 @@ export const idlFactory = ({ IDL }) => { 'g' : IDL.Func([list], [B, tree, stream], []), }); t.fill(IDL.Func([s], [], [])); + return IDL.Service({ 'f1' : IDL.Func( [list, IDL.Vec(IDL.Nat8), IDL.Opt(IDL.Bool)], @@ -129,4 +255,5 @@ export const idlFactory = ({ IDL }) => { 'bbbbb' : IDL.Func([b], [], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts index 53e68afe1..348175545 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts +++ b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export interface non_tuple { _1_ : string, _2_ : string } export type tuple = [string, string]; @@ -13,5 +13,7 @@ export interface _SERVICE { 'bib' : ActorMethod<[[bigint]], { _0_ : bigint }>, 'foo' : ActorMethod<[{ _2_ : bigint }], { _2_ : bigint, '_2' : bigint }>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.js b/rust/candid_parser/tests/assets/ok/fieldnat.js index 590e67cdd..f0154adec 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.js +++ b/rust/candid_parser/tests/assets/ok/fieldnat.js @@ -1,6 +1,40 @@ +import { IDL } from '@dfinity/candid'; + +export const tuple = IDL.Tuple(IDL.Text, IDL.Text); +export const non_tuple = IDL.Record({ _1_ : IDL.Text, _2_ : IDL.Text }); + +export const idlService = IDL.Service({ + 'bab' : IDL.Func([IDL.Int, IDL.Nat], [], []), + 'bar' : IDL.Func( + [IDL.Record({ '2' : IDL.Int })], + [IDL.Variant({ 'e20' : IDL.Null, 'e30' : IDL.Null })], + [], + ), + 'bas' : IDL.Func( + [IDL.Tuple(IDL.Int, IDL.Int)], + [IDL.Tuple(IDL.Text, IDL.Nat)], + [], + ), + 'baz' : IDL.Func( + [IDL.Record({ _2_ : IDL.Int, '2' : IDL.Nat })], + [IDL.Record({})], + [], + ), + 'bba' : IDL.Func([tuple], [non_tuple], []), + 'bib' : IDL.Func([IDL.Tuple(IDL.Int)], [IDL.Variant({ _0_ : IDL.Int })], []), + 'foo' : IDL.Func( + [IDL.Record({ _2_ : IDL.Int })], + [IDL.Record({ _2_ : IDL.Int, '_2' : IDL.Int })], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const tuple = IDL.Tuple(IDL.Text, IDL.Text); const non_tuple = IDL.Record({ _1_ : IDL.Text, _2_ : IDL.Text }); + return IDL.Service({ 'bab' : IDL.Func([IDL.Int, IDL.Nat], [], []), 'bar' : IDL.Func( @@ -31,4 +65,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index 687fa6900..0b3bd61e1 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts +++ b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type Fn = ActorMethod<[bigint], bigint>; export type Gn = Fn; @@ -23,5 +23,7 @@ export interface _SERVICE { 'high_order_fn_via_record' : ActorMethod<[R], bigint>, 'high_order_fn_via_record_inline' : ActorMethod<[RInline], bigint>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.js b/rust/candid_parser/tests/assets/ok/inline_methods.js index 1b63f3979..497784f13 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.js +++ b/rust/candid_parser/tests/assets/ok/inline_methods.js @@ -1,3 +1,34 @@ +import { IDL } from '@dfinity/candid'; + +export const Fn = IDL.Func([IDL.Nat], [IDL.Nat], ['query']); +export const Gn = Fn; +export const R = IDL.Record({ + 'x' : IDL.Nat, + 'fn' : Fn, + 'gn' : Gn, + 'nested' : IDL.Record({ 'fn' : Gn }), +}); +export const RInline = IDL.Record({ + 'x' : IDL.Nat, + 'fn' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), +}); + +export const idlService = IDL.Service({ + 'add_two' : IDL.Func([IDL.Nat], [IDL.Nat], []), + 'fn' : Fn, + 'high_order_fn' : IDL.Func([IDL.Nat, Fn], [IDL.Nat], []), + 'high_order_fn_inline' : IDL.Func( + [IDL.Nat, IDL.Func([IDL.Nat], [IDL.Nat], ['query'])], + [IDL.Nat], + [], + ), + 'high_order_fn_via_id' : IDL.Func([IDL.Nat, Gn], [Fn], []), + 'high_order_fn_via_record' : IDL.Func([R], [IDL.Nat], []), + 'high_order_fn_via_record_inline' : IDL.Func([RInline], [IDL.Nat], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const Fn = IDL.Func([IDL.Nat], [IDL.Nat], ['query']); const Gn = Fn; @@ -11,6 +42,7 @@ export const idlFactory = ({ IDL }) => { 'x' : IDL.Nat, 'fn' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), }); + return IDL.Service({ 'add_two' : IDL.Func([IDL.Nat], [IDL.Nat], []), 'fn' : Fn, @@ -25,4 +57,5 @@ export const idlFactory = ({ IDL }) => { 'high_order_fn_via_record_inline' : IDL.Func([RInline], [IDL.Nat], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 22d1c24a6..72e5c8720 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.d.ts +++ b/rust/candid_parser/tests/assets/ok/keyword.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type if_ = { 'branch' : { 'val' : bigint, 'left' : if_, 'right' : if_ } @@ -31,5 +31,7 @@ export interface _SERVICE { undefined >, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/keyword.js b/rust/candid_parser/tests/assets/ok/keyword.js index 032fe5f2a..a98f142c8 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.js +++ b/rust/candid_parser/tests/assets/ok/keyword.js @@ -1,3 +1,69 @@ +import { IDL } from '@dfinity/candid'; + +export const if_ = IDL.Rec(); +export const list = IDL.Rec(); +export const o = IDL.Rec(); +export const stream = IDL.Rec(); +export const t = IDL.Rec(); +o.fill(IDL.Opt(o)); +export const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list }); +list.fill(IDL.Opt(node)); +if_.fill( + IDL.Variant({ + 'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : if_, 'right' : if_ }), + 'leaf' : IDL.Int, + }) +); +stream.fill( + IDL.Opt( + IDL.Record({ 'head' : IDL.Nat, 'next' : IDL.Func([], [stream], ['query']) }) + ) +); +export const return_ = IDL.Service({ + 'f' : t, + 'g' : IDL.Func([list], [if_, stream], []), +}); +t.fill(IDL.Func([return_], [], [])); + +export const idlService = IDL.Service({ + 'Oneway' : IDL.Func([], [], ['oneway']), + 'f_' : IDL.Func([o], [o], []), + 'field' : IDL.Func( + [IDL.Record({ 'test' : IDL.Nat16, _1291438163_ : IDL.Nat8 })], + [IDL.Record({})], + [], + ), + 'fieldnat' : IDL.Func( + [IDL.Record({ _2_ : IDL.Int, '2' : IDL.Nat })], + [IDL.Tuple(IDL.Int)], + [], + ), + 'oneway' : IDL.Func([IDL.Nat8], [], ['oneway']), + 'oneway_' : IDL.Func([IDL.Nat8], [], ['oneway']), + 'query' : IDL.Func([IDL.Vec(IDL.Nat8)], [IDL.Vec(IDL.Nat8)], ['query']), + 'return' : IDL.Func([o], [o], []), + 'service' : t, + 'tuple' : IDL.Func( + [IDL.Tuple(IDL.Int, IDL.Vec(IDL.Nat8), IDL.Text)], + [IDL.Tuple(IDL.Int, IDL.Nat8)], + [], + ), + 'variant' : IDL.Func( + [ + IDL.Variant({ + 'A' : IDL.Null, + 'B' : IDL.Null, + 'C' : IDL.Null, + 'D' : IDL.Float64, + }), + ], + [], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const if_ = IDL.Rec(); const list = IDL.Rec(); @@ -26,6 +92,7 @@ export const idlFactory = ({ IDL }) => { 'g' : IDL.Func([list], [if_, stream], []), }); t.fill(IDL.Func([return_], [], [])); + return IDL.Service({ 'Oneway' : IDL.Func([], [], ['oneway']), 'f_' : IDL.Func([o], [o], []), @@ -63,4 +130,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index ca60e354d..63cd4a439 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type bitcoin_address = string; export type bitcoin_network = { 'mainnet' : null } | @@ -171,5 +171,7 @@ export interface _SERVICE { undefined >, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/management.js b/rust/candid_parser/tests/assets/ok/management.js index cb2b784b3..29817e94f 100644 --- a/rust/candid_parser/tests/assets/ok/management.js +++ b/rust/candid_parser/tests/assets/ok/management.js @@ -1,3 +1,235 @@ +import { IDL } from '@dfinity/candid'; + +export const bitcoin_network = IDL.Variant({ + 'mainnet' : IDL.Null, + 'testnet' : IDL.Null, +}); +export const bitcoin_address = IDL.Text; +export const get_balance_request = IDL.Record({ + 'network' : bitcoin_network, + 'address' : bitcoin_address, + 'min_confirmations' : IDL.Opt(IDL.Nat32), +}); +export const satoshi = IDL.Nat64; +export const get_current_fee_percentiles_request = IDL.Record({ + 'network' : bitcoin_network, +}); +export const millisatoshi_per_byte = IDL.Nat64; +export const get_utxos_request = IDL.Record({ + 'network' : bitcoin_network, + 'filter' : IDL.Opt( + IDL.Variant({ 'page' : IDL.Vec(IDL.Nat8), 'min_confirmations' : IDL.Nat32 }) + ), + 'address' : bitcoin_address, +}); +export const block_hash = IDL.Vec(IDL.Nat8); +export const outpoint = IDL.Record({ + 'txid' : IDL.Vec(IDL.Nat8), + 'vout' : IDL.Nat32, +}); +export const utxo = IDL.Record({ + 'height' : IDL.Nat32, + 'value' : satoshi, + 'outpoint' : outpoint, +}); +export const get_utxos_response = IDL.Record({ + 'next_page' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'tip_height' : IDL.Nat32, + 'tip_block_hash' : block_hash, + 'utxos' : IDL.Vec(utxo), +}); +export const send_transaction_request = IDL.Record({ + 'transaction' : IDL.Vec(IDL.Nat8), + 'network' : bitcoin_network, +}); +export const canister_id = IDL.Principal; +export const definite_canister_settings = IDL.Record({ + 'freezing_threshold' : IDL.Nat, + 'controllers' : IDL.Vec(IDL.Principal), + 'memory_allocation' : IDL.Nat, + 'compute_allocation' : IDL.Nat, +}); +export const canister_settings = IDL.Record({ + 'freezing_threshold' : IDL.Opt(IDL.Nat), + 'controllers' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'memory_allocation' : IDL.Opt(IDL.Nat), + 'compute_allocation' : IDL.Opt(IDL.Nat), +}); +export const ecdsa_curve = IDL.Variant({ 'secp256k1' : IDL.Null }); +export const http_header = IDL.Record({ + 'value' : IDL.Text, + 'name' : IDL.Text, +}); +export const http_response = IDL.Record({ + 'status' : IDL.Nat, + 'body' : IDL.Vec(IDL.Nat8), + 'headers' : IDL.Vec(http_header), +}); +export const wasm_module = IDL.Vec(IDL.Nat8); + +export const idlService = IDL.Service({ + 'bitcoin_get_balance' : IDL.Func([get_balance_request], [satoshi], []), + 'bitcoin_get_current_fee_percentiles' : IDL.Func( + [get_current_fee_percentiles_request], + [IDL.Vec(millisatoshi_per_byte)], + [], + ), + 'bitcoin_get_utxos' : IDL.Func([get_utxos_request], [get_utxos_response], []), + 'bitcoin_send_transaction' : IDL.Func([send_transaction_request], [], []), + 'canister_status' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [ + IDL.Record({ + 'status' : IDL.Variant({ + 'stopped' : IDL.Null, + 'stopping' : IDL.Null, + 'running' : IDL.Null, + }), + 'memory_size' : IDL.Nat, + 'cycles' : IDL.Nat, + 'settings' : definite_canister_settings, + 'idle_cycles_burned_per_day' : IDL.Nat, + 'module_hash' : IDL.Opt(IDL.Vec(IDL.Nat8)), + }), + ], + [], + ), + 'create_canister' : IDL.Func( + [IDL.Record({ 'settings' : IDL.Opt(canister_settings) })], + [IDL.Record({ 'canister_id' : canister_id })], + [], + ), + 'delete_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'deposit_cycles' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'ecdsa_public_key' : IDL.Func( + [ + IDL.Record({ + 'key_id' : IDL.Record({ 'name' : IDL.Text, 'curve' : ecdsa_curve }), + 'canister_id' : IDL.Opt(canister_id), + 'derivation_path' : IDL.Vec(IDL.Vec(IDL.Nat8)), + }), + ], + [ + IDL.Record({ + 'public_key' : IDL.Vec(IDL.Nat8), + 'chain_code' : IDL.Vec(IDL.Nat8), + }), + ], + [], + ), + 'http_request' : IDL.Func( + [ + IDL.Record({ + 'url' : IDL.Text, + 'method' : IDL.Variant({ + 'get' : IDL.Null, + 'head' : IDL.Null, + 'post' : IDL.Null, + }), + 'max_response_bytes' : IDL.Opt(IDL.Nat64), + 'body' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'transform' : IDL.Opt( + IDL.Record({ + 'function' : IDL.Func( + [ + IDL.Record({ + 'context' : IDL.Vec(IDL.Nat8), + 'response' : http_response, + }), + ], + [http_response], + ['query'], + ), + 'context' : IDL.Vec(IDL.Nat8), + }) + ), + 'headers' : IDL.Vec(http_header), + }), + ], + [http_response], + [], + ), + 'install_code' : IDL.Func( + [ + IDL.Record({ + 'arg' : IDL.Vec(IDL.Nat8), + 'wasm_module' : wasm_module, + 'mode' : IDL.Variant({ + 'reinstall' : IDL.Null, + 'upgrade' : IDL.Null, + 'install' : IDL.Null, + }), + 'canister_id' : canister_id, + }), + ], + [], + [], + ), + 'provisional_create_canister_with_cycles' : IDL.Func( + [ + IDL.Record({ + 'settings' : IDL.Opt(canister_settings), + 'specified_id' : IDL.Opt(canister_id), + 'amount' : IDL.Opt(IDL.Nat), + }), + ], + [IDL.Record({ 'canister_id' : canister_id })], + [], + ), + 'provisional_top_up_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id, 'amount' : IDL.Nat })], + [], + [], + ), + 'raw_rand' : IDL.Func([], [IDL.Vec(IDL.Nat8)], []), + 'sign_with_ecdsa' : IDL.Func( + [ + IDL.Record({ + 'key_id' : IDL.Record({ 'name' : IDL.Text, 'curve' : ecdsa_curve }), + 'derivation_path' : IDL.Vec(IDL.Vec(IDL.Nat8)), + 'message_hash' : IDL.Vec(IDL.Nat8), + }), + ], + [IDL.Record({ 'signature' : IDL.Vec(IDL.Nat8) })], + [], + ), + 'start_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'stop_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'uninstall_code' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'update_settings' : IDL.Func( + [ + IDL.Record({ + 'canister_id' : IDL.Principal, + 'settings' : canister_settings, + }), + ], + [], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const bitcoin_network = IDL.Variant({ 'mainnet' : IDL.Null, @@ -65,6 +297,7 @@ export const idlFactory = ({ IDL }) => { 'headers' : IDL.Vec(http_header), }); const wasm_module = IDL.Vec(IDL.Nat8); + return IDL.Service({ 'bitcoin_get_balance' : IDL.Func([get_balance_request], [satoshi], []), 'bitcoin_get_current_fee_percentiles' : IDL.Func( @@ -230,4 +463,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 3404ebcce..163a6de82 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursion.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type A = B; export type B = [] | [A]; @@ -17,5 +17,7 @@ export type tree = { } | { 'leaf' : bigint }; export interface _SERVICE extends s {} +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/recursion.js b/rust/candid_parser/tests/assets/ok/recursion.js index d0fdaeee8..95d11d847 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.js +++ b/rust/candid_parser/tests/assets/ok/recursion.js @@ -1,3 +1,32 @@ +import { IDL } from '@dfinity/candid'; + +export const B = IDL.Rec(); +export const list = IDL.Rec(); +export const s = IDL.Rec(); +export const stream = IDL.Rec(); +export const tree = IDL.Rec(); +export const t = IDL.Func([s], [], []); +export const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list }); +list.fill(IDL.Opt(node)); +export const A = B; +B.fill(IDL.Opt(A)); +tree.fill( + IDL.Variant({ + 'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : tree, 'right' : tree }), + 'leaf' : IDL.Int, + }) +); +stream.fill( + IDL.Opt( + IDL.Record({ 'head' : IDL.Nat, 'next' : IDL.Func([], [stream], ['query']) }) + ) +); +s.fill(IDL.Service({ 'f' : t, 'g' : IDL.Func([list], [B, tree, stream], []) })); + +export const idlService = s.getType(); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const B = IDL.Rec(); const list = IDL.Rec(); @@ -26,6 +55,8 @@ export const idlFactory = ({ IDL }) => { s.fill( IDL.Service({ 'f' : t, 'g' : IDL.Func([list], [B, tree, stream], []) }) ); + return s.getType(); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts index ba2ef3fda..f2c92fd06 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts @@ -1,8 +1,10 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export interface s { 'next' : ActorMethod<[], Principal> } export interface _SERVICE extends s {} +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.js b/rust/candid_parser/tests/assets/ok/recursive_class.js index a60a6487b..2d83bf276 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.js +++ b/rust/candid_parser/tests/assets/ok/recursive_class.js @@ -1,10 +1,22 @@ +import { IDL } from '@dfinity/candid'; + +export const s = IDL.Rec(); +s.fill(IDL.Service({ 'next' : IDL.Func([], [s], []) })); + +export const idlService = s.getType(); + +export const idlInitArgs = [s]; + export const idlFactory = ({ IDL }) => { const s = IDL.Rec(); s.fill(IDL.Service({ 'next' : IDL.Func([], [s], []) })); + return s.getType(); }; + export const init = ({ IDL }) => { const s = IDL.Rec(); s.fill(IDL.Service({ 'next' : IDL.Func([], [s], []) })); + return [s]; }; diff --git a/rust/candid_parser/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts index 57c3c871d..0378a7f42 100644 --- a/rust/candid_parser/tests/assets/ok/service.d.ts +++ b/rust/candid_parser/tests/assets/ok/service.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export type Func = ActorMethod<[], Principal>; export interface Service { 'f' : Func } @@ -18,5 +18,7 @@ export interface _SERVICE { { 'b' : { 'f' : [] | [[Principal, string]] } } >, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/service.js b/rust/candid_parser/tests/assets/ok/service.js index e7d8ffd37..9c937bc15 100644 --- a/rust/candid_parser/tests/assets/ok/service.js +++ b/rust/candid_parser/tests/assets/ok/service.js @@ -1,8 +1,34 @@ +import { IDL } from '@dfinity/candid'; + +export const Service = IDL.Rec(); +export const Func = IDL.Func([], [Service], []); +Service.fill(IDL.Service({ 'f' : Func })); +export const Service2 = Service; + +export const idlService = IDL.Service({ + 'asArray' : IDL.Func([], [IDL.Vec(Service2), IDL.Vec(Func)], ['query']), + 'asPrincipal' : IDL.Func([], [Service2, Func], []), + 'asRecord' : IDL.Func([], [IDL.Tuple(Service2, IDL.Opt(Service), Func)], []), + 'asVariant' : IDL.Func( + [], + [ + IDL.Variant({ + 'a' : Service2, + 'b' : IDL.Record({ 'f' : IDL.Opt(Func) }), + }), + ], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const Service = IDL.Rec(); const Func = IDL.Func([], [Service], []); Service.fill(IDL.Service({ 'f' : Func })); const Service2 = Service; + return IDL.Service({ 'asArray' : IDL.Func([], [IDL.Vec(Service2), IDL.Vec(Func)], ['query']), 'asPrincipal' : IDL.Func([], [Service2, Func], []), @@ -23,4 +49,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/unicode.d.ts b/rust/candid_parser/tests/assets/ok/unicode.d.ts index 1e559610c..1adc4b11b 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.d.ts +++ b/rust/candid_parser/tests/assets/ok/unicode.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@dfinity/principal'; export interface A { '\u{e000}' : bigint, @@ -18,5 +18,7 @@ export interface _SERVICE { '函数名' : ActorMethod<[A], B>, '👀' : ActorMethod<[bigint], bigint>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/unicode.js b/rust/candid_parser/tests/assets/ok/unicode.js index 9d9774fb0..c97d46f52 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.js +++ b/rust/candid_parser/tests/assets/ok/unicode.js @@ -1,3 +1,27 @@ +import { IDL } from '@dfinity/candid'; + +export const A = IDL.Record({ + '\u{e000}' : IDL.Nat, + '📦🍦' : IDL.Nat, + '字段名' : IDL.Nat, + '字 段 名2' : IDL.Nat, +}); +export const B = IDL.Variant({ + '' : IDL.Null, + '空的' : IDL.Null, + ' 空的 ' : IDL.Null, + '1⃣️2⃣️3⃣️' : IDL.Null, +}); + +export const idlService = IDL.Service({ + '' : IDL.Func([IDL.Nat], [IDL.Nat], []), + '✈️ 🚗 ⛱️ ' : IDL.Func([], [], ['oneway']), + '函数名' : IDL.Func([A], [B], []), + '👀' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const A = IDL.Record({ '\u{e000}' : IDL.Nat, @@ -11,6 +35,7 @@ export const idlFactory = ({ IDL }) => { ' 空的 ' : IDL.Null, '1⃣️2⃣️3⃣️' : IDL.Null, }); + return IDL.Service({ '' : IDL.Func([IDL.Nat], [IDL.Nat], []), '✈️ 🚗 ⛱️ ' : IDL.Func([], [], ['oneway']), @@ -18,4 +43,5 @@ export const idlFactory = ({ IDL }) => { '👀' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), }); }; + export const init = ({ IDL }) => { return []; }; From 77731176bed02159c0aeee8c5cfe95ca1d051749 Mon Sep 17 00:00:00 2001 From: Luca Bertelli Date: Thu, 9 Oct 2025 19:08:20 +0200 Subject: [PATCH 09/11] feat!: collect names for return types in parser (#684) **Overview** Named arguments can also be in the return type according to the spec. This PR updates the grammar to collect the names of the return types. As a consequence, it fixes a regression introduced in https://github.com/dfinity/candid/pull/612/files#diff-c5ff9610c5d6538ec79f3d465965d2b3a3e3b628b16854a91b4b4a90ba766a57L220-L230, in order to keep parsing the return types with arguments without errors. Updates a test case to check if the behavior has been restored. --- CHANGELOG.md | 5 +- rust/candid/src/binary_parser.rs | 5 +- rust/candid/src/pretty/candid.rs | 7 +-- rust/candid/src/ser.rs | 4 +- rust/candid/src/types/internal.rs | 30 ++++++++---- rust/candid/src/types/subtype.rs | 48 ++++--------------- rust/candid_derive/src/func.rs | 4 +- rust/candid_parser/src/bindings/analysis.rs | 8 ++-- rust/candid_parser/src/bindings/javascript.rs | 18 ++++--- rust/candid_parser/src/bindings/motoko.rs | 13 ++--- rust/candid_parser/src/bindings/rust.rs | 17 ++++--- rust/candid_parser/src/bindings/typescript.rs | 4 +- rust/candid_parser/src/grammar.lalrpop | 10 ++-- rust/candid_parser/src/syntax/mod.rs | 4 +- rust/candid_parser/src/syntax/pretty.rs | 6 +-- rust/candid_parser/src/test.rs | 6 +-- rust/candid_parser/src/typing.rs | 2 +- .../candid_parser/tests/assets/ok/service.did | 2 +- rust/candid_parser/tests/assets/service.did | 2 +- rust/candid_parser/tests/value.rs | 9 +++- tools/didc/src/main.rs | 9 ++-- 21 files changed, 104 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c47a7620..a4e98ad07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * [BREAKING]: type representation was optimized to improve performance: + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. - + The `args` field of the `candid::types::internal::Function` struct now is a `Vec` instead of `Vec`, to preserve argument names. + + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. * [BREAKING]: Removed the `candid::pretty::concat` function @@ -16,7 +16,7 @@ * Non-breaking changes: + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. + The `JavaScript` `didc` target now exports its generated IDL type objects. - + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function altneratives to `idlFactory` and `init`). + + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). + fix: subtyping and coercion rules for optional types + fix: coercion of values into nested optional types + fix: values of types `reserved` at any context do not coerce into values of type `null` @@ -26,6 +26,7 @@ * Breaking changes: + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The `rets` field in `FuncType` now has type `Vec`. * Non-breaking changes: + Supports parsing the arguments' names for `func` and `service` (init args). diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index 1c53a8e39..1226f0272 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -205,7 +205,10 @@ impl ConsType { }); } for ret in &f.rets { - rets.push(ret.to_type(len)?); + rets.push(ArgType { + name: None, + typ: ret.to_type(len)?, + }); } TypeInner::Func(Function { modes: f.ann.iter().map(|x| x.inner.clone()).collect(), diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 1d4fe42bd..9f0089663 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -156,7 +156,7 @@ fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { pub fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_named_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_named_args(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") .append(RcDoc::space()) @@ -185,11 +185,6 @@ pub fn pp_args(args: &[Type]) -> RcDoc<'_> { sep_enclose(args.iter().map(pp_ty), ",", "(", ")") } -/// Pretty-prints return types in the form of `(type1, type2)`. -pub fn pp_rets(args: &[Type]) -> RcDoc<'_> { - pp_args(args) -} - pub fn pp_mode(mode: &FuncMode) -> RcDoc<'_> { match mode { FuncMode::Oneway => RcDoc::text("oneway"), diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 53fee18ef..986789eed 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -339,7 +339,7 @@ impl TypeSerialize { self.build_type(&ty.typ)?; } for ty in &func.rets { - self.build_type(ty)?; + self.build_type(&ty.typ)?; } sleb128_encode(&mut buf, Opcode::Func as i64)?; leb128_encode(&mut buf, func.args.len() as u64)?; @@ -348,7 +348,7 @@ impl TypeSerialize { } leb128_encode(&mut buf, func.rets.len() as u64)?; for ty in &func.rets { - self.encode(&mut buf, ty)?; + self.encode(&mut buf, &ty.typ)?; } leb128_encode(&mut buf, func.modes.len() as u64)?; for m in &func.modes { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 62d8ae855..742c87e30 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -163,7 +163,14 @@ impl TypeContainer { typ: self.go(&arg.typ), }) .collect(), - rets: func.rets.iter().map(|arg| self.go(arg)).collect(), + rets: func + .rets + .iter() + .map(|arg| ArgType { + name: arg.name.clone(), + typ: self.go(&arg.typ), + }) + .collect(), }), TypeInner::Service(serv) => TypeInner::Service( serv.iter() @@ -307,7 +314,14 @@ impl Type { typ: t.typ.subst(tau), }) .collect(), - rets: func.rets.into_iter().map(|t| t.subst(tau)).collect(), + rets: func + .rets + .into_iter() + .map(|t| ArgType { + name: t.name, + typ: t.typ.subst(tau), + }) + .collect(), }) } Service(serv) => Service( @@ -398,7 +412,7 @@ pub fn text_size(t: &Type, limit: i32) -> Result { limit -= cnt; } for t in &func.rets { - cnt += text_size(t, limit)?; + cnt += text_size(&t.typ, limit)?; limit -= cnt; } cnt @@ -552,7 +566,7 @@ pub enum FuncMode { pub struct Function { pub modes: Vec, pub args: Vec, - pub rets: Vec, + pub rets: Vec, } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -587,16 +601,16 @@ impl Function { /// `func!((u8, &str) -> (Nat) query)` expands to `Type(Rc::new(TypeInner::Func(...)))` macro_rules! func { ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Query] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::Query] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) composite_query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::CompositeQuery] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::CompositeQuery] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) oneway ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Oneway] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::Oneway] })) }; } #[macro_export] diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 11fad4123..5723a25f6 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -1,5 +1,5 @@ use super::internal::{find_type, Field, Label, Type, TypeInner}; -use crate::types::TypeEnv; +use crate::types::{ArgType, TypeEnv}; use crate::{Error, Result}; use anyhow::Context; use std::collections::{HashMap, HashSet}; @@ -129,18 +129,8 @@ fn subtype_( if f1.modes != f2.modes { return Err(Error::msg("Function mode mismatch")); } - let f1_args = f1 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f2_args = f2 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let args1 = to_tuple(&f1_args); - let args2 = to_tuple(&f2_args); + let args1 = to_tuple(&f1.args); + let args2 = to_tuple(&f2.args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); subtype_(report, gamma, env, &args2, &args1) @@ -222,18 +212,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( if f1.modes != f2.modes { return Err(Error::msg("Function mode mismatch")); } - let f1_args = f1 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f2_args = f2 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let args1 = to_tuple(&f1_args); - let args2 = to_tuple(&f2_args); + let args1 = to_tuple(&f1.args); + let args2 = to_tuple(&f2.args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); equal(gamma, env, &args1, &args2).context("Mismatch in function input type")?; @@ -241,16 +221,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( Ok(()) } (Class(init1, ty1), Class(init2, ty2)) => { - let init1_typ = init1 - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let init2_typ = init2 - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let init_1 = to_tuple(&init1_typ); - let init_2 = to_tuple(&init2_typ); + let init_1 = to_tuple(init1); + let init_2 = to_tuple(init2); equal(gamma, env, &init_1, &init_2).context(format!( "Mismatch in init args: {} and {}", pp_args(init1), @@ -292,13 +264,13 @@ where } } -fn to_tuple(args: &[Type]) -> Type { +fn to_tuple(args: &[ArgType]) -> Type { TypeInner::Record( args.iter() .enumerate() - .map(|(i, ty)| Field { + .map(|(i, arg)| Field { id: Label::Id(i as u32).into(), - ty: ty.clone(), + ty: arg.typ.clone(), }) .collect(), ) diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 1b4487aa6..7e5443ccd 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -140,7 +140,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { #doc_storage let mut args: Vec = Vec::new(); #(#args)* - let mut rets: Vec = Vec::new(); + let mut rets: Vec = Vec::new(); #(#rets)* let func = Function { args, rets, modes: #modes }; service.push((#name.to_string(), TypeInner::Func(func).into())); @@ -195,7 +195,7 @@ fn generate_arg(name: TokenStream, (arg_name, ty): &(Option, String)) -> fn generate_ret(name: TokenStream, ty: &str) -> TokenStream { let ty = syn::parse_str::(ty).unwrap(); quote! { - #name.push(env.add::<#ty>()); + #name.push(ArgType { name: None, typ: env.add::<#ty>() }); } } diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index 01af68de9..cf14e2e76 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -58,7 +58,8 @@ pub fn chase_type<'a>( } Func(f) => { let args = f.args.iter().map(|arg| &arg.typ); - for ty in args.clone().chain(f.rets.iter()) { + let rets = f.rets.iter().map(|ret| &ret.typ); + for ty in args.chain(rets) { chase_type(seen, res, env, ty)?; } } @@ -117,7 +118,7 @@ pub fn chase_def_use<'a>( } for (i, arg) in func.rets.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -162,7 +163,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result { let args = f.args.iter().map(|arg| &arg.typ); - for ty in args.clone().chain(f.rets.iter()) { + let rets = f.rets.iter().map(|ret| &ret.typ); + for ty in args.chain(rets) { go(seen, res, _env, ty)?; } } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index ad750c9d1..92b6a7a98 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -161,18 +161,20 @@ fn pp_fields(fs: &[Field]) -> RcDoc<'_> { fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_args(&func.rets); let modes = pp_modes(&func.modes); sep_enclose([args, rets, modes], ",", "(", ")").nest(INDENT_SPACE) } fn pp_args(args: &[ArgType]) -> RcDoc<'_> { - let args = args.iter().map(|arg| pp_ty(&arg.typ)); - sep_enclose(args, ",", "[", "]") + pp_types(args.iter().map(|arg| &arg.typ)) } -fn pp_rets(args: &[Type]) -> RcDoc<'_> { - sep_enclose(args.iter().map(pp_ty), ",", "[", "]") +fn pp_types<'a, T>(types: T) -> RcDoc<'a> +where + T: Iterator, +{ + sep_enclose(types.map(pp_ty), ",", "[", "]") } fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc<'_> { @@ -275,7 +277,7 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { .append(";"); let idl_init_args = str("export const idlInitArgs = ") - .append(pp_rets(init_types)) + .append(pp_types(init_types.iter())) .append(";"); let idl_factory_return = kwd("return").append(actor).append(";"); @@ -286,7 +288,9 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { let init_defs = chase_types(env, init_types).unwrap(); let init_recs = infer_rec(env, &init_defs).unwrap(); let init_defs_doc = pp_defs(env, &init_defs, &init_recs, false); - let init_doc = kwd("return").append(pp_rets(init_types)).append(";"); + let init_doc = kwd("return") + .append(pp_types(init_types.iter())) + .append(";"); let init_doc = init_defs_doc.append(init_doc); let init_doc = str("export const init = ({ IDL }) => ").append(enclose_space("{", init_doc, "};")); diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 49a5a6fa2..0408b3a94 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -212,16 +212,17 @@ fn pp_args(args: &[ArgType]) -> RcDoc<'_> { } } -fn pp_rets(args: &[Type]) -> RcDoc<'_> { - match args { +fn pp_rets(rets: &[ArgType]) -> RcDoc<'_> { + match rets { [ty] => { - if is_tuple(ty) { - enclose("(", pp_ty(ty), ")") + let typ = &ty.typ; + if is_tuple(typ) { + enclose("(", pp_ty(typ), ")") } else { - pp_ty(ty) + pp_ty(typ) } } - _ => sep_enclose(args.iter().map(pp_ty), ",", "(", ")"), + _ => sep_enclose(rets.iter().map(|ret| pp_ty(&ret.typ)), ",", "(", ")"), } } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 88e609099..c21449ae9 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -619,11 +619,11 @@ fn test_{test_name}() {{ }); sep_enclose(args, ",", "(", ")") } - fn pp_rets<'b>(&mut self, rets: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + fn pp_rets<'b>(&mut self, rets: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { let tys = rets.iter().enumerate().map(|(i, t)| { let lab = format!("{prefix}{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = self.pp_ty(t, true); + let res = self.pp_ty(&t.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }); @@ -701,10 +701,10 @@ fn test_{test_name}() {{ .rets .iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, ret)| { let lab = format!("ret{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = self.pp_ty(ty, true); + let res = self.pp_ty(&ret.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }) @@ -1111,7 +1111,7 @@ impl<'b> NominalState<'_, 'b> { .rets .into_iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, ret)| { let lab = format!("ret{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); let idx = if i == 0 { @@ -1120,10 +1120,13 @@ impl<'b> NominalState<'_, 'b> { i.to_string() }; path.push(TypePath::Func(format!("ret{idx}"))); - let ty = self.nominalize(env, path, &ty, None); + let ty = self.nominalize(env, path, &ret.typ, None); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - ty + ArgType { + name: ret.name.clone(), + typ: ty, + } }) .collect(), }) diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index b53adee40..f5991791a 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -207,9 +207,9 @@ fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { let args = sep_enclose(args, ",", "[", "]"); let rets = match func.rets.len() { 0 => str("undefined"), - 1 => pp_ty(env, &func.rets[0], true), + 1 => pp_ty(env, &func.rets[0].typ, true), _ => sep_enclose( - func.rets.iter().map(|ty| pp_ty(env, ty, true)), + func.rets.iter().map(|ty| pp_ty(env, &ty.typ, true)), ",", "[", "]", diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index cfcdd2c73..dea382f75 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -217,7 +217,7 @@ VariantFieldTyp: TypeField = { =>? Ok(TypeField { label: Label::Id(id), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() }), } -ArgTupTyp: Vec = "(" > ")" =>? { +TupTyp: Vec = "(" > ")" =>? { let args = <>; let mut named_args: Vec = args.iter().filter_map(|a| a.name.clone()).collect(); named_args.sort(); @@ -225,10 +225,8 @@ ArgTupTyp: Vec = "(" > ")" =>? { Ok(args) }; -TupTyp: Vec = "(" > ")" => <>; - FuncTyp: FuncType = { - "->" => + "->" => FuncType { modes, args, rets }, } @@ -271,7 +269,7 @@ Actor: IDLType = { MainActor: IDLActorType = { "service" "id"? ":" ";"? => IDLActorType { typ: t, docs: doc_comment.unwrap_or_default() }, - "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, + "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, } pub IDLProg: IDLProg = { @@ -279,7 +277,7 @@ pub IDLProg: IDLProg = { } pub IDLInitArgs: IDLInitArgs = { - > => IDLInitArgs { decs, args } + > => IDLInitArgs { decs, args } } // Test file. Follows the "specification" in test/README.md diff --git a/rust/candid_parser/src/syntax/mod.rs b/rust/candid_parser/src/syntax/mod.rs index 3ff42cdbf..f8caf3442 100644 --- a/rust/candid_parser/src/syntax/mod.rs +++ b/rust/candid_parser/src/syntax/mod.rs @@ -51,7 +51,7 @@ impl std::str::FromStr for IDLType { #[derive(Debug, Clone)] pub struct IDLTypes { - pub args: Vec, + pub args: Vec, } impl std::str::FromStr for IDLTypes { @@ -107,7 +107,7 @@ pub enum PrimType { pub struct FuncType { pub modes: Vec, pub args: Vec, - pub rets: Vec, + pub rets: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/rust/candid_parser/src/syntax/pretty.rs b/rust/candid_parser/src/syntax/pretty.rs index 7480d336f..8c89f7c1c 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -88,7 +88,7 @@ fn pp_function(func: &FuncType) -> RcDoc<'_> { fn pp_method(func: &FuncType) -> RcDoc<'_> { let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_args(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") .append(RcDoc::space()) @@ -107,10 +107,6 @@ fn pp_args(args: &[IDLArgType]) -> RcDoc<'_> { sep_enclose(args, ",", "(", ")") } -fn pp_rets(rets: &[IDLType]) -> RcDoc<'_> { - sep_enclose(rets.iter().map(pp_ty), ",", "(", ")") -} - fn pp_service(methods: &[Binding]) -> RcDoc<'_> { kwd("service").append(pp_service_methods(methods)) } diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index 10735e941..a25ea91b7 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -1,5 +1,5 @@ use super::typing::check_prog; -use crate::syntax::{Dec, IDLProg, IDLType}; +use crate::syntax::{Dec, IDLArgType, IDLProg}; use crate::{Error, Result}; use candid::types::value::IDLArgs; use candid::types::{Type, TypeEnv}; @@ -7,7 +7,7 @@ use candid::DecoderConfig; const DECODING_COST: usize = 20_000_000; -type TupType = Vec; +type TupType = Vec; pub struct Test { pub defs: Vec, @@ -158,7 +158,7 @@ pub fn check(test: Test) -> Result<()> { print!("Checking {} {}...", i + 1, assert.desc()); let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push(super::typing::ast_to_type(&env, ty)?); + types.push(super::typing::ast_to_type(&env, &ty.typ)?); } let input = assert.left.parse(&env, &types); let pass = if let Some(assert_right) = &assert.right { diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 264fb4b23..67c2c8249 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -79,7 +79,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { } let mut t2 = Vec::new(); for t in func.rets.iter() { - t2.push(check_type(env, t)?); + t2.push(check_arg(env, t)?); } if func.modes.len() > 1 { return Err(Error::msg("cannot have more than one mode")); diff --git a/rust/candid_parser/tests/assets/ok/service.did b/rust/candid_parser/tests/assets/ok/service.did index 7dc21afac..741e020cc 100644 --- a/rust/candid_parser/tests/assets/ok/service.did +++ b/rust/candid_parser/tests/assets/ok/service.did @@ -3,7 +3,7 @@ type Service2 = Service; type Func = func () -> (Service); service : { asArray : () -> (vec Service2, vec Func) query; - asPrincipal : () -> (Service2, Func); + asPrincipal : () -> (ret0 : Service2, ret1 : Func); asRecord : () -> (record { Service2; opt Service; Func }); asVariant : () -> (variant { a : Service2; b : record { f : opt Func } }); } diff --git a/rust/candid_parser/tests/assets/service.did b/rust/candid_parser/tests/assets/service.did index cfec74e3f..43e2602bc 100644 --- a/rust/candid_parser/tests/assets/service.did +++ b/rust/candid_parser/tests/assets/service.did @@ -5,5 +5,5 @@ service : { asArray: () -> (vec Service2, vec Func) query; asRecord: () -> (record { Service2; opt Service; Func }); asVariant: () -> (variant { a: Service2; b: record { f:opt Func }}); - asPrincipal: () -> (Service2, Func); + asPrincipal: () -> (ret0 : Service2, ret1 : Func); } diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index 6199f8422..80a022ccd 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -66,10 +66,15 @@ service : { { let str = "(opt record { head = 1000; tail = opt record {head = -2000; tail = null}}, variant {a = 42})"; let args = parse_idl_args(str).unwrap(); - let encoded = args.to_bytes_with_types(&env, &method.rets).unwrap(); + let rets = method + .rets + .iter() + .map(|ret| ret.typ.clone()) + .collect::>(); + let encoded = args.to_bytes_with_types(&env, &rets).unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!(decoded.to_string(), "(\n opt record {\n 1_158_359_328 = 1_000 : int16;\n 1_291_237_008 = opt record {\n 1_158_359_328 = -2_000 : int16;\n 1_291_237_008 = null;\n };\n },\n variant { 97 = 42 : nat },\n)"); - let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &method.rets).unwrap(); + let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &rets).unwrap(); assert_eq!( decoded.to_string(), "(\n opt record {\n head = 1_000 : int16;\n tail = opt record { head = -2_000 : int16; tail = null };\n },\n variant { a = 42 : nat },\n)" diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index c6e69114d..5ae280008 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -131,7 +131,7 @@ impl TypeAnnotation { (Some(tys), None) => { let mut types = Vec::new(); for ty in tys.args.iter() { - types.push(ast_to_type(&env, ty)?); + types.push(ast_to_type(&env, &ty.typ)?); } Ok((env, types)) } @@ -139,10 +139,11 @@ impl TypeAnnotation { let actor = actor .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; let func = env.get_method(&actor, meth)?; - let types = match mode { - Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), - Mode::Decode => func.rets.clone(), + let types_iter = match mode { + Mode::Encode => func.args.iter(), + Mode::Decode => func.rets.iter(), }; + let types = types_iter.map(|arg| arg.typ.clone()).collect(); Ok((env, types)) } _ => unreachable!(), From 10aa7e38bb7ae19c6ad3d18281a2147348867f5a Mon Sep 17 00:00:00 2001 From: Raymond Khalife Date: Thu, 18 Jun 2026 21:15:46 -0400 Subject: [PATCH 10/11] fix the rust/bench --- rust/bench/Cargo.lock | 27 +++++++++++++++++++++++- rust/bench/bench.rs | 12 ++++++++--- rust/candid/fuzz/Cargo.lock | 35 ++++++++++++++++++++++++++++++-- rust/candid/src/pretty/candid.rs | 8 ++++++-- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index cd34cf046..d6b0fc946 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.100" @@ -162,6 +168,8 @@ dependencies = [ "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown 0.15.5", "hex", "ic_principal", "leb128", @@ -416,6 +424,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -453,6 +467,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -530,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 8f5256acb..5aeb941c2 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -519,15 +519,21 @@ fn subtype_decode() -> BenchResult { // code path. See https://github.com/dfinity/candid/issues/603 #[bench(raw)] fn vec_service() -> BenchResult { - use candid::types::{Function, TypeEnv, TypeInner}; + use candid::types::{ArgType, Function, TypeEnv, TypeInner}; let mut config = DecoderConfig::new(); config.set_decoding_quota(COST); let method_type: candid::types::Type = TypeInner::Func(Function { modes: vec![], - args: vec![TypeInner::Text.into()], - rets: vec![TypeInner::Nat64.into()], + args: vec![ArgType { + name: None, + typ: TypeInner::Text.into(), + }], + rets: vec![ArgType { + name: None, + typ: TypeInner::Nat64.into(), + }], }) .into(); diff --git a/rust/candid/fuzz/Cargo.lock b/rust/candid/fuzz/Cargo.lock index c046c15f3..69cc5fbaf 100644 --- a/rust/candid/fuzz/Cargo.lock +++ b/rust/candid/fuzz/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.86" @@ -66,12 +72,14 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.27" +version = "0.10.29" dependencies = [ "anyhow", "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown", "hex", "ic_principal", "leb128", @@ -97,7 +105,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.27" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", @@ -172,6 +180,18 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -182,6 +202,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hex" version = "0.4.3" diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index f23ce0bc3..ddbaba342 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -200,8 +200,12 @@ fn pp_ty_with_doc<'a>(ty: &'a Type, doc: Option<&'a TypeDoc>) -> RcDoc<'a> { match ty.as_ref() { Record(ref fs) => { if ty.is_tuple() && !has_field_docs(doc) { - let tuple = - sep_enclose_space(fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), ";", "{", "}"); + let tuple = sep_enclose_space( + fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), + ";", + "{", + "}", + ); kwd("record").append(tuple) } else { kwd("record").append(pp_fields_with_doc(fs, false, doc)) From bab84c6ce2ff924134a89824dc057f4b1a1f8956 Mon Sep 17 00:00:00 2001 From: raymondk Date: Mon, 22 Jun 2026 21:40:14 -0400 Subject: [PATCH 11/11] Update CHANGELOG with breaking and non-breaking changes --- CHANGELOG.md | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb9f8f5f..b575cc6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## Unreleased + +### Candid + +* [BREAKING]: type representation was optimized to improve performance: + + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. + + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. + + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. + +* [BREAKING]: Removed the `candid::pretty::concat` function + + `candid::pretty::enclose` and `candid::pretty:enclose_space` don't collapse the separators on empty documents anymore + +* Non-breaking changes: + + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. + + The `JavaScript` `didc` target now exports its generated IDL type objects. + + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). + +### candid_parser + +* Breaking changes: + + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The `rets` field in `FuncType` now has type `Vec`. + +* Non-breaking changes: + + Supports parsing the arguments' names for `func` and `service` (init args). + ## 2026-05-27 ### Candid 0.10.29 @@ -103,31 +130,6 @@ * Non-breaking changes: + fix: escape `*/` to prevent premature JS doc comment termination -### Candid - -* [BREAKING]: type representation was optimized to improve performance: - + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. - + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. - + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. - + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. - -* [BREAKING]: Removed the `candid::pretty::concat` function - + `candid::pretty::enclose` and `candid::pretty:enclose_space` don't collapse the separators on empty documents anymore - -* Non-breaking changes: - + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. - + The `JavaScript` `didc` target now exports its generated IDL type objects. - + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). - -### candid_parser - -* Breaking changes: - + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. - + The `rets` field in `FuncType` now has type `Vec`. - -* Non-breaking changes: - + Supports parsing the arguments' names for `func` and `service` (init args). - ## 2025-10-26 ### Candid 0.10.20