diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af64d7b..d9c3822e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ ## 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` 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. + + 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 + +* Breaking changes: + + 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-08-04 ### Candid 0.10.17 @@ -390,7 +414,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) @@ -416,7 +440,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 @@ -435,7 +459,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 @@ -475,7 +499,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) @@ -626,7 +650,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/Cargo.lock b/Cargo.lock index b83e5060..b0878a12 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.17", "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 00000000..db551b3a --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +ignore-interior-mutability = ["candid::types::type_key::TypeKey"] diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index f46f1615..813b8fe8 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]] diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 9ec60bee..5f6e0272 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/Cargo.toml b/rust/candid/Cargo.toml index 1293656a..69a0a0d8 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -20,6 +20,8 @@ candid_derive = { path = "../candid_derive", version = "=0.10.17" } 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 d0c1ea0e..1c53a8e3 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -1,4 +1,5 @@ -use crate::types::internal::{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, @@ -201,7 +199,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)?); @@ -233,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 75ca3c05..57e38781 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 79ae95d0..1d4fe42b 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] = [ @@ -69,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"), @@ -97,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"), @@ -105,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)) } @@ -128,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 { @@ -148,13 +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 { - let fields = fs.iter().map(|f| pp_field(f, is_variant)); - enclose_space("{", concat(fields, ";"), "}") +fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { + sep_enclose_space(fs.iter().map(|f| pp_field(f, is_variant)), ";", "{", "}") } -pub fn pp_function(func: &Function) -> RcDoc { - let args = pp_args(&func.args); +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); args.append(" ->") @@ -163,62 +164,74 @@ 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) + } + }); + sep_enclose(args, ",", "(", ")") +} + /// Pretty-prints arguments in the form of `(type1, type2)`. -pub fn pp_args(args: &[Type]) -> RcDoc { - let doc = concat(args.iter().map(pp_ty), ","); - enclose("(", doc, ")") +/// +/// To print named arguments, use [`pp_named_args`] instead. +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 { +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)))) } 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 { - lines(env.0.iter().map(|(id, ty)| { +fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { + 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(";") })) } -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), + TypeInner::Var(ref s) => doc.append(s.as_str()), _ => unreachable!(), } } @@ -233,10 +246,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 { @@ -496,7 +517,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 { @@ -505,9 +526,9 @@ pub mod value { pp_label_raw(&field.id).append(val_doc) } - fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc { - let fs = concat(fields.iter().map(|f| pp_field(depth, f, false)), ";"); - enclose_space("{", fs, "}") + fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc<'_> { + let fs = fields.iter().map(|f| pp_field(depth, f, false)); + sep_enclose_space(fs, ";", "{", "}") } pub fn pp_char(v: u8) -> String { @@ -518,7 +539,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:?}")); @@ -534,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)) } @@ -552,11 +573,11 @@ pub mod value { } } - pub fn pp_args(args: &IDLArgs) -> RcDoc { + pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> { let args = args .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 6127d34c..3b2844f4 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>, @@ -71,34 +38,139 @@ 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("'") .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/src/ser.rs b/rust/candid/src/ser.rs index 2ed78e7a..53fee18e 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 0bfd8cc0..62d8ae85 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,21 +140,29 @@ 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(), - 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 +170,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() @@ -188,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), @@ -196,7 +217,7 @@ pub enum TypeInner { Variant(Vec), Func(Function), Service(Vec<(String, Type)>), - Class(Vec, Type), + Class(Vec, Type), Principal, Future, } @@ -249,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)), @@ -278,7 +299,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 +315,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() @@ -331,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) => { @@ -353,7 +389,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 +551,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 +587,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 49af606d..84a4b9a7 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; @@ -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/subtype.rs b/rust/candid/src/types/subtype.rs index 5c4d9fdd..11fad412 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/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 0723f276..5f5aef1c 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 00000000..238f37b2 --- /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/tests/types.rs b/rust/candid/tests/types.rs index 688fcf2f..da0742f0 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 57742456..1b4487aa 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 edd316bc..01af68de 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)?, @@ -56,7 +57,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 +69,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 +96,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 +108,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) @@ -148,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)?, @@ -159,7 +161,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 +173,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)?; } @@ -178,8 +181,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result 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"), @@ -119,14 +119,14 @@ fn pp_ty(ty: &Type) -> 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), ")")), 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)) } @@ -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,76 +149,80 @@ 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 { - let fields = concat(fs.iter().map(pp_field), ","); - enclose_space("({", fields, "})") +fn pp_fields(fs: &[Field]) -> RcDoc<'_> { + sep_enclose_space(fs.iter().map(pp_field), ",", "({", "})") } -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); - 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: &[Type]) -> RcDoc { - let doc = concat(args.iter().map(pp_ty), ","); - enclose("[", doc, "]") +fn pp_args(args: &[ArgType]) -> RcDoc<'_> { + let args = args.iter().map(|arg| pp_ty(&arg.typ)); + sep_enclose(args, ",", "[", "]") } -fn pp_rets(args: &[Type]) -> RcDoc { - pp_args(args) +fn pp_rets(args: &[Type]) -> RcDoc<'_> { + 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, "]") +fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc<'_> { + 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, "})") +fn pp_service(serv: &[(String, Type)]) -> RcDoc<'_> { + let ms = serv + .iter() + .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))); + sep_enclose_space(ms, ",", "({", "})") } 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 ty = env.find_type(id).unwrap(); + 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) } @@ -226,10 +230,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), @@ -237,38 +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.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() + 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 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 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() } } } @@ -296,7 +333,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("_") @@ -305,17 +342,13 @@ 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 { - 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(_) => { @@ -340,28 +373,20 @@ 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), "}"), } } - pub fn pp_args(args: &IDLArgs) -> RcDoc { - let body = concat(args.args.iter().map(pp_value), ","); - enclose("[", body, "]") + pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> { + 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 195e87da..49a5a6fa 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; @@ -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"), @@ -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), @@ -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,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) + } + }); + sep_enclose(args, ",", "(", ")") } } } -fn pp_rets(args: &[Type]) -> RcDoc { - pp_args(args) +fn pp_rets(args: &[Type]) -> RcDoc<'_> { + match args { + [ty] => { + if is_tuple(ty) { + enclose("(", pp_ty(ty), ")") + } else { + pp_ty(ty) + } + } + _ => sep_enclose(args.iter().map(pp_ty), ",", "(", ")"), + } } fn pp_service<'a>(serv: &'a [(String, Type)], syntax: Option<&'a [syntax::Binding]>) -> RcDoc<'a> { @@ -219,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> { @@ -260,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> { @@ -277,10 +296,10 @@ 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 [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)), @@ -295,12 +314,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 9e358bd9..88e60909 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -4,8 +4,10 @@ use crate::{ syntax::{self, IDLActorType, IDLMergedProg, IDLType}, Deserialize, }; -use candid::pretty::utils::*; -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; use serde::Serialize; @@ -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 { @@ -129,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 != '_') @@ -150,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> { @@ -239,9 +241,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!( @@ -286,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` @@ -386,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> { @@ -427,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")); @@ -491,20 +498,21 @@ 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 } 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 @@ -515,7 +523,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()); @@ -601,24 +609,31 @@ 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 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 + }); + 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)| { let lab = format!("{prefix}{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); let res = self.pp_ty(t, true); self.state.pop_state(old, StateElem::Label(&lab)); res }); - enclose("(", concat(tys.into_iter(), ","), ")") - } - fn pp_rets<'b>(&mut self, rets: &'b [Type]) -> RcDoc<'b> { - self.pp_args(rets, "ret") + sep_enclose(tys, ",", "(", ")") } 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(" ->") @@ -643,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 } @@ -668,7 +683,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 +692,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 +742,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 +751,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()) }) @@ -794,7 +809,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 { @@ -1009,6 +1026,7 @@ 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); @@ -1054,6 +1072,7 @@ 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); @@ -1071,7 +1090,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 +1098,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 @@ -1120,8 +1142,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() { @@ -1152,8 +1174,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) => { @@ -1168,10 +1190,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), @@ -1192,12 +1217,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 d80444f9..b53adee4 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), @@ -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("_") @@ -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, ",", "{", "}") } } @@ -206,14 +203,15 @@ 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 = enclose("[", concat(args, ","), "]"); + let args = func.args.iter().map(|arg| pp_ty(env, &arg.typ, true)); + 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)), ","), "]", ), }; @@ -238,12 +236,12 @@ 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) }); - enclose_space("{", concat(methods, ","), "}") + sep_enclose_space(methods, ",", "{", "}") } fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { @@ -263,8 +261,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 +285,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 +303,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 { @@ -319,12 +317,12 @@ 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.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(), @@ -334,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/src/configs.rs b/rust/candid_parser/src/configs.rs index 4b6a57cd..e50d80a1 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/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 584be172..cfcdd2c7 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 63d3674b..bf579c8f 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".into()).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/random.rs b/rust/candid_parser/src/random.rs index 8a38e586..31ca3bbd 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), 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 60e6bba7..7480d336 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -3,12 +3,14 @@ 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, }, - syntax::{Binding, FuncType, IDLActorType, IDLMergedProg, IDLType, PrimType, TypeField}, }; -fn pp_ty(ty: &IDLType) -> RcDoc { +fn pp_ty(ty: &IDLType) -> RcDoc<'_> { use IDLType::*; match ty { PrimT(PrimType::Null) => str("null"), @@ -40,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() @@ -50,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, ";"), "}") + sep_enclose_space(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 { @@ -67,24 +69,24 @@ 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, "}")) + 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)) } } -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); @@ -94,20 +96,26 @@ fn pp_method(func: &FuncType) -> RcDoc { .nest(INDENT_SPACE) } -fn pp_args(args: &[IDLType]) -> RcDoc { - let doc = concat(args.iter().map(pp_ty), ","); - enclose("(", doc, ")") +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) + } + }); + sep_enclose(args, ",", "(", ")") } -fn pp_rets(rets: &[IDLType]) -> RcDoc { - pp_args(rets) +fn pp_rets(rets: &[IDLType]) -> RcDoc<'_> { + sep_enclose(rets.iter().map(pp_ty), ",", "(", ")") } -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 { @@ -119,11 +127,10 @@ 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 [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)), @@ -132,7 +139,7 @@ fn pp_class<'a>(args: &'a [IDLType], 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")) @@ -143,7 +150,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/src/typing.rs b/rust/candid_parser/src/typing.rs index f661dc8e..264fb4b2 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}; @@ -51,8 +51,9 @@ pub fn check_type(env: &Env, t: &IDLType) -> 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)?; @@ -74,7 +75,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 +105,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(); @@ -139,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(_) => (), } @@ -151,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 { @@ -173,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}"))); } @@ -193,7 +204,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 +273,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 1c7df1ca..d6e69aa5 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, @@ -59,7 +60,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!(), }) @@ -75,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))) @@ -115,6 +119,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 00000000..c7f3117e --- /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 00000000..0a4773fe --- /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/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts index 780b66b7..fd1c863e 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 9d59adae..b6682ed2 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/actor.mo b/rust/candid_parser/tests/assets/ok/actor.mo index 98531f3f..46d5a433 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/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index acd196f0..f10f30bc 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.did b/rust/candid_parser/tests/assets/ok/class.did index 494dcc3b..a33497f3 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.js b/rust/candid_parser/tests/assets/ok/class.js index 5f052ee0..8ab17645 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/class.mo b/rust/candid_parser/tests/assets/ok/class.mo index 8b9458c7..454a3f2f 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 c4873864..f7f94db2 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -4,12 +4,12 @@ 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 } #[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 00000000..9aa36e9c --- /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 00000000..0c2043a4 --- /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/comment.d.ts b/rust/candid_parser/tests/assets/ok/comment.d.ts index ff7d3a18..f9d1f7b5 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 1f63d478..6a3800d1 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 d75b132a..ba6ef8f3 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 528eaa6f..8a04059a 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/cyclic.mo b/rust/candid_parser/tests/assets/ok/cyclic.mo index 4ca4a600..7bae9418 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/empty.d.ts b/rust/candid_parser/tests/assets/ok/empty.d.ts index e01c5afb..2897b15f 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 74b91c7c..a4d03699 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/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index 899477cc..81d1ed30 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/escape.d.ts b/rust/candid_parser/tests/assets/ok/escape.d.ts index 54eaf207..32501995 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 b46d9ceb..68e3b466 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 dcdc5520..f8c61ea7 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.did b/rust/candid_parser/tests/assets/ok/example.did index 3dd73166..48980d55 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.js b/rust/candid_parser/tests/assets/ok/example.js index fe10e4ce..5a609879 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/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 8dd8d9bf..13381c77 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -2,31 +2,6 @@ // 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; - /// Doc comment for List - public type List = ?{ - /// Doc comment for List head - head : Int; - /// Doc comment for List tail - tail : List; - }; - public type a = { #a; #b : b }; - public type b = (Int, Nat); - /// Doc comment for broker service - public type broker = actor { - find : shared Text -> async actor { - current : shared () -> async Nat32; - up : shared () -> async (); - }; - }; - 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 : { @@ -45,6 +20,38 @@ module { ]; }; }; + /// 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 + head : Int; + /// 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 }; + /// Doc comment for broker service + public type broker = actor { + find : shared (name : Text) -> async actor { + current : shared () -> async Nat32; + 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 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,18 +92,11 @@ 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 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 - 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 d296baa0..4653172d 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)] @@ -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 @@ -167,11 +167,11 @@ 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, 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.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts index 53e68afe..34817554 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.did b/rust/candid_parser/tests/assets/ok/fieldnat.did index 7855c696..1f5e2a58 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.js b/rust/candid_parser/tests/assets/ok/fieldnat.js index 590e67cd..f0154ade 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/fieldnat.mo b/rust/candid_parser/tests/assets/ok/fieldnat.mo index 64f77183..f38c0359 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 b5399f7f..55a92604 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)] @@ -29,13 +29,13 @@ 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 } - 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/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index 687fa690..0b3bd61e 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 1b63f397..497784f1 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/inline_methods.mo b/rust/candid_parser/tests/assets/ok/inline_methods.mo index b1c8b016..f97f4ecc 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.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 22d1c24a..72e5c872 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.did b/rust/candid_parser/tests/assets/ok/keyword.did index 0a42d407..9b1d2cec 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.js b/rust/candid_parser/tests/assets/ok/keyword.js index 032fe5f2..a98f142c 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/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo index 2802968e..cf214b12 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 return_ -> async (); + 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/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index 7da7385a..313eb71c 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<()> { @@ -66,10 +66,10 @@ 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,),)> { + 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/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index ca60e354..63cd4a43 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 cb2b784b..29817e94 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/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 0609c738..83fc9074 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.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 3404ebcc..163a6de8 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.did b/rust/candid_parser/tests/assets/ok/recursion.did index 9137077e..4dd70d2e 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.js b/rust/candid_parser/tests/assets/ok/recursion.js index d0fdaeee..95d11d84 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/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo index cdb3eb51..34e72f02 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 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/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs index 3b21c9c3..23931004 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/assets/ok/recursive_class.d.ts b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts index ba2ef3fd..f2c92fd0 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 a60a6487..2d83bf27 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 57c3c871..0378a7f4 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 e7d8ffd3..9c937bc1 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/service.mo b/rust/candid_parser/tests/assets/ok/service.mo index 14ee15e7..ff6c3486 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]); diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index cbcf60cd..692b444b 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,)> { diff --git a/rust/candid_parser/tests/assets/ok/unicode.d.ts b/rust/candid_parser/tests/assets/ok/unicode.d.ts index 1e559610..1adc4b11 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 9d9774fb..c97d46f5 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 []; }; diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 4bf218ce..ee635136 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", ), _ => { diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index a860f6bb..0d2f522f 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() { @@ -42,7 +43,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, @@ -121,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); } @@ -144,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 37faecae..d8d3461e 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), })? diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index f22ce6ca..c6e69114 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!(),