diff --git a/CHANGELOG.md b/CHANGELOG.md index 3efcfa0e9..b575cc6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## Unreleased + +### Candid + +* [BREAKING]: type representation was optimized to improve performance: + + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. + + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. + + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. + +* [BREAKING]: Removed the `candid::pretty::concat` function + + `candid::pretty::enclose` and `candid::pretty:enclose_space` don't collapse the separators on empty documents anymore + +* Non-breaking changes: + + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. + + The `JavaScript` `didc` target now exports its generated IDL type objects. + + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). + +### candid_parser + +* Breaking changes: + + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The `rets` field in `FuncType` now has type `Vec`. + +* Non-breaking changes: + + Supports parsing the arguments' names for `func` and `service` (init args). + ## 2026-05-27 ### Candid 0.10.29 @@ -539,7 +566,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) @@ -565,7 +592,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 @@ -584,7 +611,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 @@ -624,7 +651,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) @@ -775,7 +802,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 2529594e2..52b23c12d 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.29", "candid_parser 0.3.2", + "foldhash", + "hashbrown 0.15.5", "hex", "ic_principal 0.1.3", "leb128", @@ -613,6 +621,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" @@ -680,6 +694,17 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.5.0" @@ -737,7 +762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..db551b3a9 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +ignore-interior-mutability = ["candid::types::type_key::TypeKey"] diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index cd34cf046..d6b0fc946 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.100" @@ -162,6 +168,8 @@ dependencies = [ "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown 0.15.5", "hex", "ic_principal", "leb128", @@ -416,6 +424,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -453,6 +467,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -530,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 7dd94f413..5aeb941c2 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -269,10 +269,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"); @@ -514,15 +519,21 @@ fn subtype_decode() -> BenchResult { // code path. See https://github.com/dfinity/candid/issues/603 #[bench(raw)] fn vec_service() -> BenchResult { - use candid::types::{Function, TypeEnv, TypeInner}; + use candid::types::{ArgType, Function, TypeEnv, TypeInner}; let mut config = DecoderConfig::new(); config.set_decoding_quota(COST); let method_type: candid::types::Type = TypeInner::Func(Function { modes: vec![], - args: vec![TypeInner::Text.into()], - rets: vec![TypeInner::Nat64.into()], + args: vec![ArgType { + name: None, + typ: TypeInner::Text.into(), + }], + rets: vec![ArgType { + name: None, + typ: TypeInner::Nat64.into(), + }], }) .into(); diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index f46667c74..038a5eace 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -20,6 +20,8 @@ candid_derive = { path = "../candid_derive", version = "=0.10.29" } 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/fuzz/Cargo.lock b/rust/candid/fuzz/Cargo.lock index c046c15f3..69cc5fbaf 100644 --- a/rust/candid/fuzz/Cargo.lock +++ b/rust/candid/fuzz/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.86" @@ -66,12 +72,14 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.27" +version = "0.10.29" dependencies = [ "anyhow", "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown", "hex", "ic_principal", "leb128", @@ -97,7 +105,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.27" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", @@ -172,6 +180,18 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -182,6 +202,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hex" version = "0.4.3" diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index 5a0bd2e4b..545701161 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}; @@ -142,9 +143,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 { @@ -152,7 +150,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, @@ -208,10 +206,16 @@ 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)?); + rets.push(ArgType { + name: None, + typ: ret.to_type(len)?, + }); } TypeInner::Func(Function { modes: f.ann.iter().map(|x| x.inner.clone()).collect(), @@ -240,13 +244,12 @@ impl ConsType { } impl Table { fn to_env(&self, len: u64) -> Result { - use std::collections::BTreeMap; - let mut env = BTreeMap::new(); + let mut env = TypeMap::default(); for (i, t) in self.table.iter().enumerate() { let ty = t .to_type(len) .with_context(|| format!("Invalid table entry {i}: {t:?}"))?; - env.insert(index_to_var(i as i64), ty); + env.insert(TypeKey::indexed(i as i64), ty); } // validate method has func type for t in env.values() { diff --git a/rust/candid/src/lib.rs b/rust/candid/src/lib.rs index 75ca3c057..57e38781a 100644 --- a/rust/candid/src/lib.rs +++ b/rust/candid/src/lib.rs @@ -303,7 +303,7 @@ pub mod pretty; // Candid hash function comes from // https://caml.inria.fr/pub/papers/garrigue-polymorphic_variants-ml98.pdf -// Not public API. Only used by tests. +// Not public API. // Remember to update the same function in candid_derive if you change this function. #[doc(hidden)] #[inline] diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index cb04d68bc..ddbaba342 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use crate::pretty::utils::*; use crate::types::{ - Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, TypeDocs, TypeEnv, - TypeInner, + ArgType, Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, TypeDocs, + TypeEnv, TypeInner, }; use pretty::RcDoc; @@ -100,7 +100,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"), @@ -108,8 +108,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)) } @@ -158,8 +158,7 @@ pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc<'_> { } fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { - let fields = fs.iter().map(|f| pp_field(f, is_variant)); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fs.iter().map(|f| pp_field(f, is_variant)), ";", "{", "}") } fn pp_field_with_doc<'a>( @@ -188,7 +187,7 @@ fn pp_fields_with_doc<'a>( let field_doc = doc.and_then(|doc| doc.fields.get(&field.id.get_id())); pp_field_with_doc(field, is_variant, field_doc) }); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fields, ";", "{", "}") } fn has_field_docs(doc: Option<&TypeDoc>) -> bool { @@ -201,8 +200,13 @@ fn pp_ty_with_doc<'a>(ty: &'a Type, doc: Option<&'a TypeDoc>) -> RcDoc<'a> { match ty.as_ref() { Record(ref fs) => { if ty.is_tuple() && !has_field_docs(doc) { - let tuple = concat(fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), ";"); - kwd("record").append(enclose_space("{", tuple, "}")) + let tuple = sep_enclose_space( + fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), + ";", + "{", + "}", + ); + kwd("record").append(tuple) } else { kwd("record").append(pp_fields_with_doc(fs, false, doc)) } @@ -213,8 +217,8 @@ fn pp_ty_with_doc<'a>(ty: &'a Type, doc: Option<&'a TypeDoc>) -> RcDoc<'a> { } pub fn pp_function(func: &Function) -> RcDoc<'_> { - let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let args = pp_named_args(&func.args); + let rets = pp_named_args(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") .append(RcDoc::space()) @@ -222,15 +226,25 @@ pub fn pp_function(func: &Function) -> RcDoc<'_> { .nest(INDENT_SPACE) } -/// 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, ")") +/// 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 return types in the form of `(type1, type2)`. -pub fn pp_rets(args: &[Type]) -> RcDoc<'_> { - pp_args(args) +/// Pretty-prints arguments in the form of `(type1, type2)`. +/// +/// To print named arguments, use [`pp_named_args`] instead. +pub fn pp_args(args: &[Type]) -> RcDoc<'_> { + sep_enclose(args.iter().map(pp_ty), ",", "(", ")") } pub fn pp_mode(mode: &FuncMode) -> RcDoc<'_> { @@ -245,28 +259,25 @@ pub fn pp_modes(modes: &[FuncMode]) -> RcDoc<'_> { } fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> RcDoc<'a> { - let doc = concat( - serv.iter().map(|(id, func)| { - let doc = docs - .and_then(|docs| docs.lookup_service_method(id)) - .map(|docs| pp_docs(docs)) - .unwrap_or(RcDoc::nil()); - let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => pp_function(f), - TypeInner::Var(_) => pp_ty(func), - _ => unreachable!(), - }; - doc.append(pp_text(id)).append(kwd(" :")).append(func_doc) - }), - ";", - ); - enclose_space("{", doc, "}") + let methods = serv.iter().map(|(id, func)| { + let doc = docs + .and_then(|docs| docs.lookup_service_method(id)) + .map(|docs| pp_docs(docs)) + .unwrap_or(RcDoc::nil()); + let func_doc = match func.as_ref() { + TypeInner::Func(ref f) => pp_function(f), + TypeInner::Var(_) => pp_ty(func), + _ => unreachable!(), + }; + doc.append(pp_text(id)).append(kwd(" :")).append(func_doc) + }); + sep_enclose_space(methods, ";", "{", "}") } fn pp_defs_plain(env: &TypeEnv) -> RcDoc<'_> { - lines(env.0.iter().map(|(id, ty)| { + lines(env.to_sorted_iter().map(|(id, ty)| { kwd("type") - .append(ident(id)) + .append(ident(id.as_str())) .append(kwd("=")) .append(pp_ty(ty)) .append(";") @@ -274,22 +285,22 @@ fn pp_defs_plain(env: &TypeEnv) -> RcDoc<'_> { } fn pp_defs<'a>(env: &'a TypeEnv, docs: &'a DocComments) -> RcDoc<'a> { - lines(env.0.iter().map(|(id, ty)| { - let type_doc = docs.lookup_type_def(id); + lines(env.to_sorted_iter().map(|(id, ty)| { + let type_doc = docs.lookup_type_def(id.as_str()); maybe_pp_docs(type_doc.map(|doc| doc.docs.as_slice())) .append(kwd("type")) - .append(ident(id)) + .append(ident(id.as_str())) .append(kwd("=")) .append(pp_ty_with_doc(ty, type_doc)) .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!(), } } @@ -304,10 +315,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_plain(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_plain(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 { @@ -594,8 +613,8 @@ pub mod value { } fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc<'_> { - let fs = concat(fields.iter().map(|f| pp_field(depth, f, false)), ";"); - enclose_space("{", fs, "}") + let fs = fields.iter().map(|f| pp_field(depth, f, false)); + sep_enclose_space(fs, ";", "{", "}") } pub fn pp_char(v: u8) -> String { @@ -622,13 +641,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)) } @@ -645,6 +664,6 @@ pub mod value { .args .iter() .map(|v| pp_value(MAX_ELEMENTS_FOR_PRETTY_PRINT, v)); - enclose("(", concat(args, ","), ")") + sep_enclose(args, ",", "(", ")") } } diff --git a/rust/candid/src/pretty/utils.rs b/rust/candid/src/pretty/utils.rs index 9e1a911ef..3b2844f41 100644 --- a/rust/candid/src/pretty/utils.rs +++ b/rust/candid/src/pretty/utils.rs @@ -1,45 +1,26 @@ -use pretty::RcDoc; +use pretty::{RcAllocator, RcDoc}; pub const INDENT_SPACE: isize = 2; pub const LINE_WIDTH: usize = 80; -fn is_empty(doc: &RcDoc) -> bool { - use pretty::Doc::*; - match &**doc { - Nil => true, - FlatAlt(t1, t2) => is_empty(t2) || is_empty(t1), - Union(t1, t2) => is_empty(t1) && is_empty(t2), - Group(t) | Nest(_, t) | Annotated((), t) => is_empty(t), - _ => false, - } -} - pub fn enclose<'a>(left: &'a str, doc: RcDoc<'a>, right: &'a str) -> RcDoc<'a> { - if is_empty(&doc) { - RcDoc::text(left).append(right) - } else { - RcDoc::text(left) - .append(RcDoc::line_()) - .append(doc) - .nest(INDENT_SPACE) - .append(RcDoc::line_()) - .append(right) - .group() - } + RcDoc::text(left) + .append(RcDoc::line_()) + .append(doc) + .nest(INDENT_SPACE) + .append(RcDoc::line_()) + .append(right) + .group() } pub fn enclose_space<'a>(left: &'a str, doc: RcDoc<'a>, right: &'a str) -> RcDoc<'a> { - if is_empty(&doc) { - RcDoc::text(left).append(right) - } else { - RcDoc::text(left) - .append(RcDoc::line()) - .append(doc) - .nest(INDENT_SPACE) - .append(RcDoc::line()) - .append(right) - .group() - } + RcDoc::text(left) + .append(RcDoc::line()) + .append(doc) + .nest(INDENT_SPACE) + .append(RcDoc::line()) + .append(right) + .group() } /// Intersperse the separator between each item in `docs`. @@ -50,20 +31,6 @@ where RcDoc::intersperse(docs, RcDoc::text(sep).append(RcDoc::line())) } -/// Append the separator to each item in `docs`. If it is displayed in a single line, omit the last separator. -pub fn concat<'a, D>(docs: D, sep: &'a str) -> RcDoc<'a> -where - D: Iterator>, -{ - let mut docs = docs.peekable(); - if docs.peek().is_none() { - return RcDoc::nil(); - } - let singleline = RcDoc::intersperse(docs, RcDoc::text(sep).append(RcDoc::line())); - let multiline = singleline.clone().append(sep); - multiline.flat_alt(singleline) -} - pub fn lines<'a, D>(docs: D) -> RcDoc<'a> where D: Iterator>, @@ -90,15 +57,120 @@ pub fn quote_ident(id: &str) -> RcDoc<'_> { .append(RcDoc::space()) } +/// Separate each item in `docs` with the separator `sep`, and enclose the result in `open` and `close`. +/// When placed on multiple lines, the last element gets a trailing separator. +pub fn sep_enclose<'a, D, S, O, C>(docs: D, sep: S, open: O, close: C) -> RcDoc<'a> +where + D: IntoIterator>, + S: pretty::Pretty<'a, RcAllocator>, + O: pretty::Pretty<'a, RcAllocator>, + C: pretty::Pretty<'a, RcAllocator>, +{ + let sep = sep.pretty(&RcAllocator); + let elems = RcDoc::intersperse(docs, sep.clone().append(RcDoc::line())); + open.pretty(&RcAllocator) + .into_doc() + .append(RcDoc::line_()) + .append(elems) + .append(sep.flat_alt(RcDoc::nil())) + .nest(INDENT_SPACE) + .append(RcDoc::line_()) + .append(close.pretty(&RcAllocator)) + .group() +} + +/// Like `sep_enclose`, but inserts a space between the opening delimiter and the first element, +/// and between the last element and the closing delimiter when placed on a single line. +pub fn sep_enclose_space<'a, D, S, O, C>(docs: D, sep: S, open: O, close: C) -> RcDoc<'a> +where + D: IntoIterator>, + S: pretty::Pretty<'a, RcAllocator>, + O: pretty::Pretty<'a, RcAllocator>, + C: pretty::Pretty<'a, RcAllocator>, +{ + let mut docs = docs.into_iter().peekable(); + if docs.peek().is_none() { + return open.pretty(&RcAllocator).append(close).into_doc(); + } + let open = open + .pretty(&RcAllocator) + .append(RcDoc::nil().flat_alt(RcDoc::space())); + let close = RcDoc::nil().flat_alt(RcDoc::space()).append(close); + sep_enclose(docs, sep, open, close) +} + #[cfg(test)] -mod test { +mod tests { use super::*; #[test] - fn concat_empty() { - let t = concat(vec![].into_iter(), ",") + fn enclose_empty() { + let t = sep_enclose(vec![], ",", "(", ")") .pretty(LINE_WIDTH) .to_string(); - assert_eq!(t, ""); + assert_eq!(t, "()"); + } + + #[test] + fn enclose_single_line() { + let printed = sep_enclose(vec![str("a"), str("b")], ",", "(", ")") + .pretty(LINE_WIDTH) + .to_string(); + assert_eq!(printed, "(a, b)"); + } + + #[test] + fn enclose_multiline() { + let docs: Vec = vec![ + str("Very long line to make sure we get a multiline document"), + str("Very long line to make sure we get a multiline document"), + ]; + let printed = sep_enclose(docs, ",", "(", ")").pretty(20).to_string(); + assert_eq!( + printed, + " +( + Very long line to make sure we get a multiline document, + Very long line to make sure we get a multiline document, +) +" + .trim() + ); + } + + #[test] + fn enclose_empty_space() { + let t = sep_enclose_space(vec![], ",", "(", ")") + .pretty(LINE_WIDTH) + .to_string(); + assert_eq!(t, "()"); + } + + #[test] + fn enclose_single_line_space() { + let printed = sep_enclose_space(vec![str("a"), str("b")], ",", "(", ")") + .pretty(LINE_WIDTH) + .to_string(); + assert_eq!(printed, "( a, b )"); + } + + #[test] + fn enclose_multiline_space() { + let docs: Vec = vec![ + str("Very long line to make sure we get a multiline document"), + str("Very long line to make sure we get a multiline document"), + ]; + let printed = sep_enclose_space(docs, ",", "(", ")") + .pretty(20) + .to_string(); + assert_eq!( + printed, + " +( + Very long line to make sure we get a multiline document, + Very long line to make sure we get a multiline document, +)" + .trim() + ); } } diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 6f7bdfdb2..6713b1db7 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -339,20 +339,20 @@ 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)?; + self.build_type(&ty.typ)?; } 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 { - self.encode(&mut buf, ty)?; + self.encode(&mut buf, &ty.typ)?; } leb128_encode(&mut buf, func.modes.len() as u64)?; for m in &func.modes { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index e11364879..cd5f11a88 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 { @@ -150,9 +155,10 @@ impl TypeContainer { let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { let name = id.to_string(); - self.env.0.insert(name.clone(), res); + let type_key = TypeKey::from(name.clone()); + self.env.0.insert(type_key.clone(), res); self.remember_named_doc(&id, &name); - TypeInner::Var(name) + 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. @@ -172,33 +178,56 @@ impl TypeContainer { let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { let name = id.to_string(); - self.env.0.insert(name.clone(), res); + let type_key = TypeKey::from(name.clone()); + self.env.0.insert(type_key.clone(), res); self.remember_named_doc(&id, &name); - TypeInner::Var(name) + 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); + let name = id.to_string(); + let type_key = TypeKey::from(name.clone()); + self.env.0.insert(type_key.clone(), ty); self.remember_named_doc(id, &name); - TypeInner::Var(name) + TypeInner::Var(type_key) } TypeInner::Func(func) => TypeInner::Func(Function { modes: func.modes.clone(), - args: func.args.iter().map(|arg| self.go(arg)).collect(), - rets: func.rets.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| ArgType { + name: arg.name.clone(), + typ: self.go(&arg.typ), + }) + .collect(), }), TypeInner::Service(serv) => TypeInner::Service( serv.iter() .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() @@ -236,7 +265,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), @@ -244,7 +273,7 @@ pub enum TypeInner { Variant(Vec), Func(Function), Service(Vec<(String, Type)>), - Class(Vec, Type), + Class(Vec, Type), Principal, Future, } @@ -297,12 +326,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)), @@ -326,8 +355,22 @@ impl Type { let func = func.clone(); Func(Function { modes: func.modes, - args: func.args.into_iter().map(|t| t.subst(tau)).collect(), - rets: func.rets.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| ArgType { + name: t.name, + typ: t.typ.subst(tau), + }) + .collect(), }) } Service(serv) => Service( @@ -335,7 +378,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() @@ -379,7 +430,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) => { @@ -401,11 +452,16 @@ 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 { - cnt += text_size(t, limit)?; + cnt += text_size(&t.typ, limit)?; limit -= cnt; } cnt @@ -558,8 +614,14 @@ pub enum FuncMode { #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] pub struct Function { pub modes: Vec, - pub args: Vec, - pub rets: 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")] @@ -588,16 +650,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()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Query] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::Query] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) composite_query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::CompositeQuery] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::CompositeQuery] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) oneway ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*], rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Oneway] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::Oneway] })) }; } #[macro_export] diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 06a28daf8..4102a9198 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -15,8 +15,8 @@ pub mod type_env; pub mod value; pub use self::internal::{ - get_type, Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, TypeDocs, - TypeId, TypeInner, + get_type, ArgType, Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, + TypeDocs, TypeId, TypeInner, }; pub use type_env::TypeEnv; @@ -31,6 +31,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 bf3027aff..11e535df2 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -1,5 +1,5 @@ use super::internal::{find_type, Field, Label, Type, TypeInner}; -use crate::types::TypeEnv; +use crate::types::{ArgType, TypeEnv}; use crate::utils::RecursionDepth; use crate::{Error, Result}; use anyhow::Context; @@ -441,8 +441,8 @@ fn check_func_params( report: OptReport, gamma: &mut Gamma, env: &TypeEnv, - sub_params: &[Type], - sup_params: &[Type], + sub_params: &[ArgType], + sup_params: &[ArgType], depth: &RecursionDepth, path: &mut Vec, errors: &mut Vec, @@ -457,8 +457,8 @@ fn check_func_params( report, gamma, env, - &sub_params[0], - &sup_params[0], + &sub_params[0].typ, + &sup_params[0].typ, depth, path, errors, @@ -797,20 +797,20 @@ where } } -fn to_tuple(args: &[Type]) -> Type { +fn to_tuple(args: &[ArgType]) -> Type { TypeInner::Record( args.iter() .enumerate() - .map(|(i, ty)| Field { + .map(|(i, arg)| Field { id: Label::Id(i as u32).into(), - ty: ty.clone(), + ty: arg.typ.clone(), }) .collect(), ) .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(); @@ -821,7 +821,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 d66e209fd..0d22f7900 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,18 +1,23 @@ +use crate::types::internal::TypeKey; use crate::types::{Function, Type, TypeInner}; use crate::utils::RecursionDepth; 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")); } @@ -20,11 +25,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); @@ -36,18 +41,19 @@ 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> { self.rec_find_type_with_depth(name, &RecursionDepth::new()) } pub(crate) fn rec_find_type_with_depth( &self, - name: &str, + name: &TypeKey, depth: &RecursionDepth, ) -> Result<&Type> { let _guard = depth.guard()?; @@ -115,8 +121,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, depth: &RecursionDepth, ) -> Result { let _guard = depth.guard()?; @@ -152,24 +158,34 @@ impl TypeEnv { Ok(result) } pub fn replace_empty(&mut self) -> Result<()> { - let mut res = BTreeMap::new(); - for name in self.0.keys() { - self.is_empty(&mut res, name, &RecursionDepth::new())?; + let mut res = HashMap::default(); + for key in self.0.keys() { + self.is_empty(&mut res, key, &RecursionDepth::new())?; } let ids: Vec<_> = res - .iter() + .into_iter() .filter(|(_, v)| matches!(v, Some(true))) - .map(|(id, _)| id.to_string()) + .map(|(id, _)| id.to_owned()) .collect(); for id in ids { self.0.insert(id, TypeInner::Empty.into()); } Ok(()) } + + /// Creates an iterator that iterates over the types in the order of keys. + /// + /// The implementation collects elements into a temporary vector and sorts the vector. + pub fn to_sorted_iter(&self) -> impl Iterator { + let mut vec: Vec<_> = self.0.iter().collect(); + vec.sort_unstable_by_key(|elem| elem.0); + vec.into_iter() + } } + impl std::fmt::Display for TypeEnv { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for (k, v) in &self.0 { + for (k, v) in self.to_sorted_iter() { writeln!(f, "type {k} = {v}")?; } Ok(()) diff --git a/rust/candid/src/types/type_key.rs b/rust/candid/src/types/type_key.rs new file mode 100644 index 000000000..238f37b29 --- /dev/null +++ b/rust/candid/src/types/type_key.rs @@ -0,0 +1,230 @@ +use crate::idl_hash; +use std::cell::OnceCell; +use std::cmp::Ordering; +use std::fmt; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; + +#[derive(Clone)] +enum TypeKeyInner { + Indexed { index: i64, name: OnceCell }, + Named { name: String, hash: u32 }, +} + +impl TypeKeyInner { + pub fn as_str(&self) -> &str { + match self { + TypeKeyInner::Indexed { index, name } => { + name.get_or_init(|| TypeKey::name_from_index(*index)) + } + TypeKeyInner::Named { name, .. } => name, + } + } +} + +impl Debug for TypeKeyInner { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// A `TypeKey` represents a type from a Candid schema. The type can be string-based or index-based. +#[derive(Debug, Clone)] +pub struct TypeKey(TypeKeyInner); + +impl TypeKey { + const INDEXED_PREFIX: &'static str = "table"; + + pub fn indexed(idx: i64) -> Self { + TypeKeyInner::Indexed { + index: idx, + name: Default::default(), + } + .into() + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + fn name_from_index(i: i64) -> String { + format!("{}{}", Self::INDEXED_PREFIX, i) + } + + fn maybe_indexed(value: &str) -> Option { + let index = i64::from_str(value.strip_prefix(Self::INDEXED_PREFIX)?).ok()?; + Some(TypeKey::indexed(index)) + } +} + +impl From for TypeKey { + fn from(value: TypeKeyInner) -> Self { + Self(value) + } +} + +impl PartialOrd for TypeKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TypeKey { + fn cmp(&self, other: &Self) -> Ordering { + // If the performance of this function ever becomes too slow, the function can be optimized + // by specializing comparison of two TypeKeyInner::Indexed objects. + self.as_str().cmp(other.as_str()) + } +} + +impl PartialEq for TypeKey { + fn eq(&self, other: &Self) -> bool { + match (&self.0, &other.0) { + ( + TypeKeyInner::Indexed { index: index1, .. }, + TypeKeyInner::Indexed { index: index2, .. }, + ) => *index1 == *index2, + ( + TypeKeyInner::Named { + hash: hash1, + name: name1, + }, + TypeKeyInner::Named { + hash: hash2, + name: name2, + }, + ) => *hash1 == *hash2 && name1 == name2, + _ => false, + } + } +} + +impl Eq for TypeKey {} + +impl Hash for TypeKey { + fn hash(&self, state: &mut H) { + match &self.0 { + TypeKeyInner::Indexed { index, .. } => index.hash(state), + TypeKeyInner::Named { hash, .. } => hash.hash(state), + } + } +} + +impl Display for TypeKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl From for TypeKey { + fn from(value: String) -> Self { + Self::maybe_indexed(&value).unwrap_or_else(|| { + TypeKeyInner::Named { + hash: idl_hash(&value), + name: value, + } + .into() + }) + } +} + +impl From<&str> for TypeKey { + fn from(value: &str) -> Self { + Self::maybe_indexed(value).unwrap_or_else(|| { + TypeKeyInner::Named { + hash: idl_hash(value), + name: value.to_string(), + } + .into() + }) + } +} + +impl AsRef for TypeKey { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn type_key_indexed_name() { + assert_eq!(TypeKey::indexed(42).as_str(), "table42"); + } + + #[test] + fn type_key_from_string() { + assert_eq!(TypeKey::from("foobar"), TypeKey::from("foobar".to_string())); + assert_eq!(TypeKey::from("foobar").as_str(), "foobar"); + assert_eq!(TypeKey::from("table").as_str(), "table"); + // Check that indexed keys with a "broken" index are parsed correctly. + assert_eq!(TypeKey::from("table3.5").as_str(), "table3.5"); + assert_eq!(TypeKey::from("table-33").as_str(), "table-33"); + } + + #[test] + fn type_key_indexed() { + assert_eq!(TypeKey::from("table33"), TypeKey::indexed(33)); + } + + #[test] + fn type_key_hash() { + let mut map = HashMap::new(); + map.insert(TypeKey::from("a"), 1); + map.insert(TypeKey::from("bar".to_string()), 2); + map.insert(TypeKey::from("table1"), 3); + map.insert(TypeKey::from("table97"), 4); + map.insert(TypeKey::indexed(33), 5); + + assert_eq!(map.get(&"a".to_string().into()), Some(&1)); + assert_eq!(map.get(&"bar".into()), Some(&2)); + assert_eq!(map.get(&TypeKey::indexed(1)), Some(&3)); + assert_eq!(map.get(&TypeKey::indexed(97)), Some(&4)); + assert_eq!(map.get(&TypeKey::from("table33")), Some(&5)); + } + + #[test] + fn type_key_ord() { + let mut keys = vec![ + TypeKey::indexed(4), + TypeKey::indexed(24), + TypeKey::from("table3"), + TypeKey::from("table"), + TypeKey::indexed(1), + TypeKey::from("table23"), + TypeKey::from("table4.3"), + TypeKey::from("zzz"), + TypeKey::from("a"), + ]; + + let expected = vec![ + TypeKey::from("a"), + TypeKey::from("table"), + TypeKey::indexed(1), + TypeKey::from("table23"), + // Note that even though 3 < 24 and 4 < 24, they're ordered afterward because we use + // string-based ordering to maintain consistency between named and indexed TypeKeys. + TypeKey::indexed(24), + TypeKey::from("table3"), + TypeKey::indexed(4), + TypeKey::from("table4.3"), + TypeKey::from("zzz"), + ]; + keys.sort_unstable(); + assert_eq!(keys, expected); + } + + #[test] + fn type_key_to_string() { + assert_eq!(TypeKey::indexed(32).to_string(), "table32"); + assert_eq!(TypeKey::from("foo").to_string(), "foo"); + // debug string + assert_eq!(format!("{:?}", TypeKey::indexed(32)), "TypeKey(table32)"); + assert_eq!(format!("{:?}", TypeKey::from("foo")), "TypeKey(foo)"); + } +} diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index 5de3ab2df..0d37f8f04 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,7 +336,22 @@ 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 65cb1cc95..ed31f9ec1 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::{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,9 +138,9 @@ 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(); + let mut rets: Vec = Vec::new(); #(#rets)* let func = Function { args, rets, modes: #modes }; service.push((#name.to_string(), TypeInner::Func(func).into())); @@ -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(); @@ -182,15 +182,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(ArgType { name: None, typ: env.add::<#ty>() }); + } } fn get_args(sig: &Signature) -> Result<(ParsedArgs, ParsedRets)> { @@ -202,7 +209,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 2633c6f08..cf14e2e76 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}; @@ -42,10 +43,12 @@ pub fn chase_type<'a>( ) -> Result<()> { use TypeInner::*; match t.as_ref() { - Var(id) if seen.insert(id) => { - let t = env.find_type(id)?; - chase_type(seen, res, env, t)?; - res.push(id); + Var(id) => { + if seen.insert(id.as_str()) { + let t = env.find_type(id)?; + chase_type(seen, res, env, t)?; + res.push(id.as_str()); + } } Opt(ty) | Vec(ty) => chase_type(seen, res, env, ty)?, Record(fs) | Variant(fs) => { @@ -54,7 +57,9 @@ 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); + let rets = f.rets.iter().map(|ret| &ret.typ); + for ty in args.chain(rets) { chase_type(seen, res, env, ty)?; } } @@ -65,7 +70,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)?; } @@ -92,7 +97,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) @@ -104,7 +109,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) @@ -113,7 +118,7 @@ pub fn chase_def_use<'a>( } for (i, arg) in func.rets.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -145,8 +150,10 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result Result<()> { use TypeInner::*; match t.as_ref() { - Var(id) if seen.insert(id) => { - res.insert(id); + Var(id) => { + if seen.insert(id.as_str()) { + res.insert(id.as_str()); + } } Opt(ty) | Vec(ty) => go(seen, res, _env, ty)?, Record(fs) | Variant(fs) => { @@ -155,7 +162,9 @@ 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); + let rets = f.rets.iter().map(|ret| &ret.typ); + for ty in args.chain(rets) { go(seen, res, _env, ty)?; } } @@ -166,7 +175,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)?; } @@ -174,8 +183,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result RcDoc<'_> { Text => str("IDL.Text"), Reserved => str("IDL.Reserved"), Empty => str("IDL.Empty"), - Var(ref s) => ident(s), + Var(ref s) => ident(s.as_str()), Principal => str("IDL.Principal"), Opt(ref t) => str("IDL.Opt").append(enclose("(", pp_ty(t), ")")), Vec(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), Record(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ","); - str("IDL.Tuple").append(enclose("(", tuple, ")")) + let fs = fs.iter().map(|f| pp_ty(&f.ty)); + str("IDL.Tuple").append(sep_enclose(fs, ",", "(", ")")) } else { str("IDL.Record").append(pp_fields(fs)) } @@ -156,69 +156,75 @@ fn pp_field(field: &Field) -> RcDoc<'_> { } fn pp_fields(fs: &[Field]) -> RcDoc<'_> { - let fields = concat(fs.iter().map(pp_field), ","); - enclose_space("({", fields, "})") + sep_enclose_space(fs.iter().map(pp_field), ",", "({", "})") } fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_args(&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<'_> { + pp_types(args.iter().map(|arg| &arg.typ)) } -fn pp_rets(args: &[Type]) -> RcDoc<'_> { - pp_args(args) +fn pp_types<'a, T>(types: T) -> RcDoc<'a> +where + T: Iterator, +{ + sep_enclose(types.map(pp_ty), ",", "[", "]") } fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc<'_> { - let doc = concat( - modes - .iter() - .map(|m| str("'").append(pp_mode(m)).append("'")), - ",", - ); - enclose("[", doc, "]") + let ms = modes + .iter() + .map(|m| str("'").append(pp_mode(m)).append("'")); + sep_enclose(ms, ",", "[", "]") } fn pp_service(serv: &[(String, Type)]) -> RcDoc<'_> { - let doc = concat( - serv.iter() - .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))), - ",", - ); - enclose_space("({", doc, "})") + let ms = serv + .iter() + .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))); + sep_enclose_space(ms, ",", "({", "})") } fn pp_defs<'a>( 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 +232,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 +243,73 @@ 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_types(init_types.iter())) + .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_types(init_types.iter())) + .append(";"); let init_doc = init_defs_doc.append(init_doc); let init_doc = str("export const init = ({ IDL }) => ").append(enclose_space("{", init_doc, "};")); 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() } } } @@ -311,10 +352,6 @@ pub mod value { .append(pp_value(&field.val)) } - fn pp_fields(fields: &[IDLField]) -> RcDoc<'_> { - concat(fields.iter().map(pp_field), ",") - } - pub fn pp_value(v: &IDLValue) -> RcDoc<'_> { use IDLValue::*; match v { @@ -340,20 +377,13 @@ pub mod value { Text(s) => RcDoc::text(format!("'{}'", s.escape_debug())), None => RcDoc::text("[]"), Opt(v) => enclose_space("[", pp_value(v), "]"), - Blob(blob) => { - let body = concat(blob.iter().map(RcDoc::as_string), ","); - enclose_space("[", body, "]") - } - Vec(vs) => { - let body = concat(vs.iter().map(pp_value), ","); - enclose_space("[", body, "]") - } + Blob(blob) => sep_enclose_space(blob.iter().map(RcDoc::as_string), ",", "[", "]"), + Vec(vs) => sep_enclose_space(vs.iter().map(pp_value), ",", "[", "]"), Record(fields) => { if is_tuple(v) { - let tuple = concat(fields.iter().map(|f| pp_value(&f.val)), ","); - enclose_space("[", tuple, "]") + sep_enclose_space(fields.iter().map(|f| pp_value(&f.val)), ",", "[", "]") } else { - enclose_space("{", pp_fields(fields), "}") + sep_enclose_space(fields.iter().map(pp_field), ",", "{", "}") } } Variant(v) => enclose_space("{", pp_field(&v.0), "}"), @@ -361,7 +391,6 @@ pub mod value { } pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> { - let body = concat(args.args.iter().map(pp_value), ","); - enclose("[", body, "]") + sep_enclose(args.args.iter().map(pp_value), ",", "[", "]") } } diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 326b7fc61..500f5baed 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; @@ -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), @@ -185,24 +185,45 @@ 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(rets: &[ArgType]) -> RcDoc<'_> { + match rets { + [ty] => { + let typ = &ty.typ; + if is_tuple(typ) { + enclose("(", pp_ty(typ), ")") + } else { + pp_ty(typ) + } + } + _ => sep_enclose(rets.iter().map(|ret| pp_ty(&ret.typ)), ",", "(", ")"), + } } fn pp_service<'a>(serv: &'a [(String, Type)], syntax: Option<&'a [syntax::Binding]>) -> RcDoc<'a> { @@ -219,12 +240,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 +280,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 +297,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 +315,12 @@ fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { fn pp_defs<'a>(env: &'a TypeEnv, prog: &'a IDLMergedProg) -> RcDoc<'a> { lines(env.0.iter().map(|(id, ty)| { - let syntax = prog.lookup(id); + let syntax = prog.lookup(id.as_str()); let docs = syntax .map(|b| pp_docs(b.docs.as_ref())) .unwrap_or(RcDoc::nil()); docs.append(kwd("public type")) - .append(escape(id, false)) + .append(escape(id.as_str(), false)) .append(" = ") .append(pp_ty_rich(ty, syntax.map(|b| &b.typ))) .append(";") diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index ef1439dab..5cae7fd1c 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; @@ -18,7 +20,7 @@ use identifier::{to_identifier_case, IdentifierCase}; 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 { @@ -212,9 +214,15 @@ impl<'a> State<'a> { .map(|(k, v)| (k.clone(), v.clone())) .collect(), ); - let src = candid::pretty::candid::pp_init_args(&env, std::slice::from_ref(src)) - .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!( @@ -259,7 +267,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` @@ -354,11 +362,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> { @@ -395,8 +403,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")); @@ -459,20 +466,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 @@ -483,7 +491,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()); @@ -569,24 +577,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)| { - let lab = format!("{prefix}{i}"); + 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, true); + let res = self.pp_ty(&t.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }); - enclose("(", concat(tys.into_iter(), ","), ")") + sep_enclose(args, ",", "(", ")") } - fn pp_rets<'b>(&mut self, rets: &'b [Type]) -> RcDoc<'b> { - self.pp_args(rets, "ret") + fn pp_rets<'b>(&mut self, rets: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { + let tys = rets.iter().enumerate().map(|(i, t)| { + let lab = format!("{prefix}{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self.pp_ty(&t.typ, true); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }); + 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(" ->") @@ -611,7 +626,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 } @@ -636,7 +651,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 @@ -645,7 +660,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) }) @@ -654,10 +669,10 @@ fn test_{test_name}() {{ .rets .iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, ret)| { let lab = format!("ret{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = self.pp_ty(ty, true); + let res = self.pp_ty(&ret.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }) @@ -695,7 +710,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 @@ -704,7 +719,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()) }) @@ -763,7 +778,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 { @@ -981,6 +998,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); @@ -1026,6 +1044,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); @@ -1043,7 +1062,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() @@ -1051,17 +1070,20 @@ 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 .rets .into_iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, ret)| { let lab = format!("ret{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); let idx = if i == 0 { @@ -1070,10 +1092,13 @@ impl<'b> NominalState<'_, 'b> { i.to_string() }; path.push(TypePath::Func(format!("ret{idx}"))); - let ty = self.nominalize(env, path, &ty, None); + let ty = self.nominalize(env, path, &ret.typ, None); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - ty + ArgType { + name: ret.name.clone(), + typ: ty, + } }) .collect(), }) @@ -1092,8 +1117,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() { @@ -1124,8 +1149,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) => { @@ -1140,10 +1165,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), @@ -1164,12 +1192,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 673d80488..5fe2e4ca1 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), @@ -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( + 1 => pp_ty(env, &func.rets[0].typ, true), + _ => sep_enclose( + func.rets.iter().map(|ty| pp_ty(env, &ty.typ, 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, ",", "{", "}") } /// Escapes doc comment content to prevent comment injection attacks. @@ -268,8 +266,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 @@ -292,7 +290,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)) @@ -310,7 +308,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 { @@ -329,7 +327,7 @@ import type { ActorMethod } from '@icp-sdk/core/agent'; import type { IDL } from '@icp-sdk/core/candid'; "#; let syntax_actor = prog.resolve_actor().ok().flatten(); - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); + let def_list: Vec<_> = env.to_sorted_iter().map(|pair| pair.0.as_str()).collect(); let defs = pp_defs(env, &def_list, prog); let actor = match actor { None => RcDoc::nil(), @@ -339,6 +337,10 @@ import type { IDL } from '@icp-sdk/core/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 4b6a57cde..e50d80a19 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -393,7 +393,7 @@ fn path_name(t: &Type) -> String { TypeInner::Text => "text", TypeInner::Reserved => "reserved", TypeInner::Empty => "empty", - TypeInner::Var(id) => id, + TypeInner::Var(id) => id.as_str(), TypeInner::Knot(id) => id.name, TypeInner::Principal => "principal", TypeInner::Opt(_) => "opt", diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 584be1729..dea382f75 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,22 @@ VariantFieldTyp: TypeField = { =>? Ok(TypeField { label: Label::Id(id), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() }), } -TupTyp: Vec = "(" > ")" => <>; +TupTyp: 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) +}; FuncTyp: FuncType = { "->" => FuncType { modes, args, rets }, } -ArgTyp: IDLType = { - Typ => <>, - Name ":" => <>, +ArgTyp: IDLArgType = { + => IDLArgType::new(t), + ":" => IDLArgType::new_with_name(t, n), } FuncMode: FuncMode = { diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 63d3674b4..bf579c8f4 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -69,7 +69,7 @@ //! //! let method = env.get_method(&actor, "g").unwrap(); //! assert_eq!(method.is_query(), true); -//! assert_eq!(method.args, 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 8a38e586d..31ca3bbdf 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -300,7 +300,7 @@ fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option), VariantT(Vec), ServT(Vec), - ClassT(Vec, Box), + ClassT(Vec, Box), PrincipalT, } @@ -51,7 +51,7 @@ impl std::str::FromStr for IDLType { #[derive(Debug, Clone)] pub struct IDLTypes { - pub args: Vec, + pub args: Vec, } impl std::str::FromStr for IDLTypes { @@ -106,8 +106,32 @@ pub enum PrimType { #[derive(Debug, Clone, PartialEq, Eq)] pub struct FuncType { pub modes: Vec, - pub args: Vec, - pub rets: 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)] @@ -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 1b1b1970e..8c89f7c1c 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -3,9 +3,11 @@ 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<'_> { @@ -52,7 +54,7 @@ fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc<'_> { fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc<'_> { let fields = fs.iter().map(|f| pp_field(f, is_variant)); - enclose_space("{", concat(fields, ";"), "}") + sep_enclose_space(fields, ";", "{", "}") } fn pp_opt(ty: &IDLType) -> RcDoc<'_> { @@ -69,8 +71,8 @@ fn pp_vec(ty: &IDLType) -> RcDoc<'_> { fn pp_record(fs: &[TypeField], is_tuple: bool) -> RcDoc<'_> { if is_tuple { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ";"); - kwd("record").append(enclose_space("{", tuple, "}")) + let fs = fs.iter().map(|f| pp_ty(&f.typ)); + kwd("record").append(sep_enclose_space(fs, ";", "{", "}")) } else { kwd("record").append(pp_fields(fs, false)) } @@ -86,7 +88,7 @@ fn pp_function(func: &FuncType) -> RcDoc<'_> { fn pp_method(func: &FuncType) -> RcDoc<'_> { let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_args(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") .append(RcDoc::space()) @@ -94,13 +96,15 @@ 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_rets(rets: &[IDLType]) -> RcDoc<'_> { - pp_args(rets) +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_service(methods: &[Binding]) -> RcDoc<'_> { @@ -119,11 +123,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)), diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index 10735e941..a25ea91b7 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -1,5 +1,5 @@ use super::typing::check_prog; -use crate::syntax::{Dec, IDLProg, IDLType}; +use crate::syntax::{Dec, IDLArgType, IDLProg}; use crate::{Error, Result}; use candid::types::value::IDLArgs; use candid::types::{Type, TypeEnv}; @@ -7,7 +7,7 @@ use candid::DecoderConfig; const DECODING_COST: usize = 20_000_000; -type TupType = Vec; +type TupType = Vec; pub struct Test { pub defs: Vec, @@ -158,7 +158,7 @@ pub fn check(test: Test) -> Result<()> { print!("Checking {} {}...", i + 1, assert.desc()); let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push(super::typing::ast_to_type(&env, ty)?); + types.push(super::typing::ast_to_type(&env, &ty.typ)?); } let input = assert.left.parse(&env, &types); let pass = if let Some(assert_right) = &assert.right { diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 5179a66fc..6d0e7336a 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,12 +1,13 @@ 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::internal::TypeKey; +use candid::types::{ArgType, Field, Function, Type, TypeEnv, TypeInner}; use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; @@ -51,8 +52,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,11 +76,11 @@ 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() { - t2.push(check_type(env, t)?); + t2.push(check_arg(env, t)?); } if func.modes.len() > 1 { return Err(Error::msg("cannot have more than one mode")); @@ -104,6 +106,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 +148,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 +160,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 { @@ -170,17 +179,17 @@ fn check_cycle(env: &TypeEnv) -> Result<()> { Ok(()) } -fn validate_func(env: &TypeEnv, seen: &mut BTreeMap, func: &Function) -> Result<()> { +fn validate_func(env: &TypeEnv, seen: &mut BTreeMap, func: &Function) -> Result<()> { for arg in func.args.iter() { - validate_type(env, seen, arg)?; + validate_type(env, seen, &arg.typ)?; } for ret in func.rets.iter() { - validate_type(env, seen, ret)?; + validate_type(env, seen, &ret.typ)?; } Ok(()) } -fn validate_type(env: &TypeEnv, seen: &mut BTreeMap, t: &Type) -> Result<()> { +fn validate_type(env: &TypeEnv, seen: &mut BTreeMap, t: &Type) -> Result<()> { match t.as_ref() { TypeInner::Null | TypeInner::Bool @@ -229,7 +238,7 @@ fn validate_type(env: &TypeEnv, seen: &mut BTreeMap, t: &Type) -> } TypeInner::Class(args, ty) => { for arg in args.iter() { - validate_type(env, seen, arg)?; + validate_type(env, seen, &arg.typ)?; } validate_type(env, seen, ty) } @@ -247,7 +256,10 @@ fn validate_decs(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}"))); } @@ -267,7 +279,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)?; @@ -336,13 +348,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 03b0d333e..b9aa58c5e 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,4 +1,5 @@ use crate::{check_prog, pretty_check_file, pretty_parse, Error, Result}; +use candid::types::internal::TypeKey; use candid::{ types::{Type, TypeInner}, TypeEnv, @@ -78,7 +79,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!(), }) @@ -94,8 +98,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))) @@ -134,6 +138,6 @@ pub fn check_rust_type(candid_args: &str) -> Result<()> { let ty = rust_env.add::(); let ty = env.merge_type(rust_env.env, ty); let mut gamma = std::collections::HashSet::new(); - equal(&mut gamma, &env, &args[0], &ty)?; + equal(&mut gamma, &env, &args[0].typ, &ty)?; Ok(()) } diff --git a/rust/candid_parser/tests/assets/collision_arguments.did b/rust/candid_parser/tests/assets/collision_arguments.did new file mode 100644 index 000000000..c7f3117e0 --- /dev/null +++ b/rust/candid_parser/tests/assets/collision_arguments.did @@ -0,0 +1,4 @@ +type f = func (a : nat, b : bool, a : text) -> (); +service : { + f : f; +}; diff --git a/rust/candid_parser/tests/assets/collision_arguments2.did b/rust/candid_parser/tests/assets/collision_arguments2.did new file mode 100644 index 000000000..0a4773fe6 --- /dev/null +++ b/rust/candid_parser/tests/assets/collision_arguments2.did @@ -0,0 +1,3 @@ +service : (b : nat, bool, b : text) -> { + f : (text, text) -> (); +}; diff --git a/rust/candid_parser/tests/assets/ok/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts index ce5b7ef2b..80ef1a413 100644 --- a/rust/candid_parser/tests/assets/ok/actor.d.ts +++ b/rust/candid_parser/tests/assets/ok/actor.d.ts @@ -13,5 +13,7 @@ export interface _SERVICE { 'h2' : h, 'o' : ActorMethod<[o], o>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/actor.js b/rust/candid_parser/tests/assets/ok/actor.js index 9d59adae4..b6682ed29 100644 --- a/rust/candid_parser/tests/assets/ok/actor.js +++ b/rust/candid_parser/tests/assets/ok/actor.js @@ -1,9 +1,28 @@ +import { IDL } from '@dfinity/candid'; + +export const o = IDL.Rec(); +export const f = IDL.Func([IDL.Int8], [IDL.Int8], []); +export const h = IDL.Func([f], [f], []); +export const g = f; +o.fill(IDL.Opt(o)); + +export const idlService = IDL.Service({ + 'f' : IDL.Func([IDL.Nat], [h], []), + 'g' : f, + 'h' : g, + 'h2' : h, + 'o' : IDL.Func([o], [o], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const o = IDL.Rec(); const f = IDL.Func([IDL.Int8], [IDL.Int8], []); const h = IDL.Func([f], [f], []); const g = f; o.fill(IDL.Opt(o)); + return IDL.Service({ 'f' : IDL.Func([IDL.Nat], [h], []), 'g' : f, @@ -12,4 +31,5 @@ export const idlFactory = ({ IDL }) => { 'o' : IDL.Func([o], [o], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/actor.mo b/rust/candid_parser/tests/assets/ok/actor.mo index 98531f3fc..46d5a4332 100644 --- a/rust/candid_parser/tests/assets/ok/actor.mo +++ b/rust/candid_parser/tests/assets/ok/actor.mo @@ -2,10 +2,10 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type f = shared Int8 -> async Int8; public type g = f; - public type h = shared f -> async f; public type o = ?o; + public type f = shared Int8 -> async Int8; + public type h = shared f -> async f; public type Self = actor { f : shared Nat -> async h; g : f; diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index 9a35748bc..7a68e4073 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -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 494dcc3bf..a33497f3e 100644 --- a/rust/candid_parser/tests/assets/ok/class.did +++ b/rust/candid_parser/tests/assets/ok/class.did @@ -1,7 +1,7 @@ type Profile = record { age : nat8; name : text }; type List = opt record { int; List }; // Doc comment for class service -service : (int, List, Profile) -> { +service : (int, l : List, Profile) -> { // Doc comment for get method in class service get : () -> (List); set : (List) -> (List); diff --git a/rust/candid_parser/tests/assets/ok/class.js b/rust/candid_parser/tests/assets/ok/class.js index 5f052ee01..8ab176452 100644 --- a/rust/candid_parser/tests/assets/ok/class.js +++ b/rust/candid_parser/tests/assets/ok/class.js @@ -1,15 +1,31 @@ +import { IDL } from '@dfinity/candid'; + +export const List = IDL.Rec(); +List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List))); +export const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text }); + +export const idlService = IDL.Service({ + 'get' : IDL.Func([], [List], []), + 'set' : IDL.Func([List], [List], []), +}); + +export const idlInitArgs = [IDL.Int, List, Profile]; + export const idlFactory = ({ IDL }) => { const List = IDL.Rec(); List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List))); const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text }); + return IDL.Service({ 'get' : IDL.Func([], [List], []), 'set' : IDL.Func([List], [List], []), }); }; + export const init = ({ IDL }) => { const List = IDL.Rec(); List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List))); const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text }); + return [IDL.Int, List, Profile]; }; diff --git a/rust/candid_parser/tests/assets/ok/class.mo b/rust/candid_parser/tests/assets/ok/class.mo index 8b9458c7f..454a3f2f8 100644 --- a/rust/candid_parser/tests/assets/ok/class.mo +++ b/rust/candid_parser/tests/assets/ok/class.mo @@ -5,7 +5,7 @@ module { public type List = ?(Int, List); public type Profile = { age : Nat8; name : Text }; /// Doc comment for class service - public type Self = (Int, List, Profile) -> async actor { + public type Self = (Int, l : List, Profile) -> async actor { /// Doc comment for get method in class service get : shared () -> async List; set : shared List -> async List; diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index c48738646..f7f94db2d 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 000000000..9aa36e9cc --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/collision_arguments.fail @@ -0,0 +1 @@ +Candid parser error: label 'a' hash collision with 'a' diff --git a/rust/candid_parser/tests/assets/ok/collision_arguments2.fail b/rust/candid_parser/tests/assets/ok/collision_arguments2.fail new file mode 100644 index 000000000..0c2043a40 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/collision_arguments2.fail @@ -0,0 +1 @@ +Candid parser error: label 'b' hash collision with 'b' diff --git a/rust/candid_parser/tests/assets/ok/comment.js b/rust/candid_parser/tests/assets/ok/comment.js index 1f63d4782..6a3800d13 100644 --- a/rust/candid_parser/tests/assets/ok/comment.js +++ b/rust/candid_parser/tests/assets/ok/comment.js @@ -1,2 +1,5 @@ -const id = IDL.Nat8; +import { IDL } from '@dfinity/candid'; + +export const id = IDL.Nat8; + diff --git a/rust/candid_parser/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts index 908e3e263..3c9470181 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.d.ts +++ b/rust/candid_parser/tests/assets/ok/cyclic.d.ts @@ -9,5 +9,7 @@ export type X = Y; export type Y = Z; export type Z = A; export interface _SERVICE { 'f' : ActorMethod<[A, B, C, X, Y, Z], undefined> } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.js b/rust/candid_parser/tests/assets/ok/cyclic.js index 528eaa6f3..8a04059a4 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.js +++ b/rust/candid_parser/tests/assets/ok/cyclic.js @@ -1,3 +1,19 @@ +import { IDL } from '@dfinity/candid'; + +export const A = IDL.Rec(); +export const C = A; +export const B = IDL.Opt(C); +A.fill(IDL.Opt(B)); +export const Z = A; +export const Y = Z; +export const X = Y; + +export const idlService = IDL.Service({ + 'f' : IDL.Func([A, B, C, X, Y, Z], [], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const A = IDL.Rec(); const C = A; @@ -6,6 +22,8 @@ export const idlFactory = ({ IDL }) => { const Z = A; const Y = Z; const X = Y; + return IDL.Service({ 'f' : IDL.Func([A, B, C, X, Y, Z], [], []) }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.mo b/rust/candid_parser/tests/assets/ok/cyclic.mo index 4ca4a600a..7bae9418e 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.mo +++ b/rust/candid_parser/tests/assets/ok/cyclic.mo @@ -3,10 +3,10 @@ module { public type A = ?B; - public type B = ?C; - public type C = A; + public type Z = A; public type X = Y; + public type C = A; public type Y = Z; - public type Z = A; + public type B = ?C; public type Self = actor { f : shared (A, B, C, X, Y, Z) -> async () } } diff --git a/rust/candid_parser/tests/assets/ok/empty.d.ts b/rust/candid_parser/tests/assets/ok/empty.d.ts index 315885bfb..ab794a341 100644 --- a/rust/candid_parser/tests/assets/ok/empty.d.ts +++ b/rust/candid_parser/tests/assets/ok/empty.d.ts @@ -8,5 +8,7 @@ export interface _SERVICE { 'g' : ActorMethod<[T], { 'a' : T }>, 'h' : ActorMethod<[[T, never]], { 'a' : T } | { 'b' : {} }>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/empty.js b/rust/candid_parser/tests/assets/ok/empty.js index 74b91c7cc..a4d036997 100644 --- a/rust/candid_parser/tests/assets/ok/empty.js +++ b/rust/candid_parser/tests/assets/ok/empty.js @@ -1,6 +1,24 @@ +import { IDL } from '@dfinity/candid'; + +export const T = IDL.Rec(); +T.fill(IDL.Tuple(T)); + +export const idlService = IDL.Service({ + 'f' : IDL.Func([IDL.Record({})], [IDL.Variant({})], []), + 'g' : IDL.Func([T], [IDL.Variant({ 'a' : T })], []), + 'h' : IDL.Func( + [IDL.Tuple(T, IDL.Empty)], + [IDL.Variant({ 'a' : T, 'b' : IDL.Record({}) })], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const T = IDL.Rec(); T.fill(IDL.Tuple(T)); + return IDL.Service({ 'f' : IDL.Func([IDL.Record({})], [IDL.Variant({})], []), 'g' : IDL.Func([T], [IDL.Variant({ 'a' : T })], []), @@ -11,4 +29,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index 899477cc8..81d1ed30c 100644 --- a/rust/candid_parser/tests/assets/ok/empty.rs +++ b/rust/candid_parser/tests/assets/ok/empty.rs @@ -9,7 +9,7 @@ pub struct FArg {} #[derive(CandidType, Deserialize)] pub enum FRet {} #[derive(CandidType, Deserialize)] -pub struct T (pub Box,); +pub struct T (pub Box); #[derive(CandidType, Deserialize)] pub enum GRet { #[serde(rename="a")] A(Box) } #[derive(CandidType, Deserialize)] @@ -23,7 +23,7 @@ impl Service { pub async fn g(&self, arg0: &T) -> Result<(GRet,)> { ic_cdk::call(self.0, "g", (arg0,)).await } - pub async fn h(&self, arg0: &(T,candid::Empty,)) -> Result<(HRet,)> { + pub async fn h(&self, arg0: &(T, candid::Empty)) -> Result<(HRet,)> { ic_cdk::call(self.0, "h", (arg0,)).await } } diff --git a/rust/candid_parser/tests/assets/ok/escape.d.ts b/rust/candid_parser/tests/assets/ok/escape.d.ts index 0908479b7..08ff97894 100644 --- a/rust/candid_parser/tests/assets/ok/escape.d.ts +++ b/rust/candid_parser/tests/assets/ok/escape.d.ts @@ -9,5 +9,7 @@ export interface t { '\\\n\'\"' : bigint, } export interface _SERVICE { '\n\'\"\'\'\"\"\r\t' : ActorMethod<[t], undefined> } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/escape.js b/rust/candid_parser/tests/assets/ok/escape.js index b46d9cebf..68e3b466d 100644 --- a/rust/candid_parser/tests/assets/ok/escape.js +++ b/rust/candid_parser/tests/assets/ok/escape.js @@ -1,3 +1,18 @@ +import { IDL } from '@dfinity/candid'; + +export const t = IDL.Record({ + '\"' : IDL.Nat, + '\'' : IDL.Nat, + '\"\'' : IDL.Nat, + '\\\n\'\"' : IDL.Nat, +}); + +export const idlService = IDL.Service({ + '\n\'\"\'\'\"\"\r\t' : IDL.Func([t], [], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const t = IDL.Record({ '\"' : IDL.Nat, @@ -5,6 +20,8 @@ export const idlFactory = ({ IDL }) => { '\"\'' : IDL.Nat, '\\\n\'\"' : IDL.Nat, }); + return IDL.Service({ '\n\'\"\'\'\"\"\r\t' : IDL.Func([t], [], []) }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index de431ffb9..c8fdceff7 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -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 3dd731662..48980d55d 100644 --- a/rust/candid_parser/tests/assets/ok/example.did +++ b/rust/candid_parser/tests/assets/ok/example.did @@ -10,7 +10,7 @@ type List = opt record { type f = func (List, func (int32) -> (int64)) -> (opt List, res); // Doc comment for broker service type broker = service { - find : (text) -> (service { current : () -> (nat32); up : () -> () }); + find : (name : text) -> (service { current : () -> (nat32); up : () -> () }); }; // Doc comment for nested type type nested = record { @@ -77,14 +77,14 @@ type tree = variant { }; // Doc comment for service id type s = service { f : t; g : (list) -> (B, tree, stream) }; -type t = func (s) -> (); +type t = func (server : s) -> (); type stream = opt record { head : nat; next : func () -> (stream) query }; type b = record { int; nat }; type a = variant { a; b : b }; // Doc comment for service service : { // Doc comment for f1 method of service - f1 : (list, blob, opt bool) -> () oneway; + f1 : (list, test : blob, opt bool) -> () oneway; g1 : (my_type, List, opt List, nested) -> (int, broker, nested_res) query; h : (vec opt text, variant { A : nat; B : opt text }, opt List) -> ( record { diff --git a/rust/candid_parser/tests/assets/ok/example.js b/rust/candid_parser/tests/assets/ok/example.js index fe10e4cef..5a609879c 100644 --- a/rust/candid_parser/tests/assets/ok/example.js +++ b/rust/candid_parser/tests/assets/ok/example.js @@ -1,3 +1,128 @@ +import { IDL } from '@dfinity/candid'; + +export const B = IDL.Rec(); +export const List = IDL.Rec(); +export const list = IDL.Rec(); +export const stream = IDL.Rec(); +export const t = IDL.Rec(); +export const tree = IDL.Rec(); +export const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list }); +list.fill(IDL.Opt(node)); +export const my_type = IDL.Principal; +List.fill(IDL.Opt(IDL.Record({ 'head' : IDL.Int, 'tail' : List }))); +export const nested = IDL.Record({ + _0_ : IDL.Nat, + _1_ : IDL.Nat, + _2_ : IDL.Tuple(IDL.Nat, IDL.Int), + _3_ : IDL.Record({ _0_ : IDL.Nat, _42_ : IDL.Nat, _43_ : IDL.Nat8 }), + _40_ : IDL.Nat, + _41_ : IDL.Variant({ + _42_ : IDL.Null, + 'A' : IDL.Null, + 'B' : IDL.Null, + 'C' : IDL.Null, + }), + _42_ : IDL.Nat, +}); +export const broker = IDL.Service({ + 'find' : IDL.Func( + [IDL.Text], + [ + IDL.Service({ + 'current' : IDL.Func([], [IDL.Nat32], []), + 'up' : IDL.Func([], [], []), + }), + ], + [], + ), +}); +export const nested_res = IDL.Variant({ + 'Ok' : IDL.Variant({ 'Ok' : IDL.Null, 'Err' : IDL.Null }), + 'Err' : IDL.Variant({ + 'Ok' : IDL.Record({ 'content' : IDL.Text }), + 'Err' : IDL.Tuple(IDL.Int), + }), +}); +export const res = IDL.Variant({ + 'Ok' : IDL.Tuple(IDL.Int, IDL.Nat), + 'Err' : IDL.Record({ 'error' : IDL.Text }), +}); +export const f = IDL.Func( + [List, IDL.Func([IDL.Int32], [IDL.Int64], [])], + [IDL.Opt(List), res], + [], + ); +export const b = IDL.Tuple(IDL.Int, IDL.Nat); +export const a = IDL.Variant({ 'a' : IDL.Null, 'b' : b }); +export const nested_records = IDL.Record({ + 'nested' : IDL.Opt(IDL.Record({ 'nested_field' : IDL.Text })), +}); +export const my_variant = IDL.Variant({ + 'a' : IDL.Record({ 'b' : IDL.Text }), + 'c' : IDL.Opt( + IDL.Record({ 'd' : IDL.Text, 'e' : IDL.Vec(IDL.Record({ 'f' : IDL.Nat })) }) + ), +}); +export const A = B; +B.fill(IDL.Opt(A)); +tree.fill( + IDL.Variant({ + 'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : tree, 'right' : tree }), + 'leaf' : IDL.Int, + }) +); +stream.fill( + IDL.Opt( + IDL.Record({ 'head' : IDL.Nat, 'next' : IDL.Func([], [stream], ['query']) }) + ) +); +export const s = IDL.Service({ + 'f' : t, + 'g' : IDL.Func([list], [B, tree, stream], []), +}); +t.fill(IDL.Func([s], [], [])); + +export const idlService = IDL.Service({ + 'f1' : IDL.Func([list, IDL.Vec(IDL.Nat8), IDL.Opt(IDL.Bool)], [], ['oneway']), + 'g1' : IDL.Func( + [my_type, List, IDL.Opt(List), nested], + [IDL.Int, broker, nested_res], + ['query'], + ), + 'h' : IDL.Func( + [ + IDL.Vec(IDL.Opt(IDL.Text)), + IDL.Variant({ 'A' : IDL.Nat, 'B' : IDL.Opt(IDL.Text) }), + IDL.Opt(List), + ], + [IDL.Record({ _42_ : IDL.Record({}), 'id' : IDL.Nat })], + [], + ), + 'i' : f, + 'x' : IDL.Func( + [a, b], + [ + IDL.Opt(a), + IDL.Opt(b), + IDL.Variant({ + 'Ok' : IDL.Record({ 'result' : IDL.Text }), + 'Err' : IDL.Variant({ 'a' : IDL.Null, 'b' : IDL.Null }), + }), + ], + ['composite_query'], + ), + 'y' : IDL.Func( + [nested_records], + [IDL.Tuple(nested_records, my_variant)], + ['query'], + ), + 'f' : t, + 'g' : IDL.Func([list], [B, tree, stream], []), + 'bbbbb' : IDL.Func([b], [], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const B = IDL.Rec(); const List = IDL.Rec(); @@ -86,6 +211,7 @@ export const idlFactory = ({ IDL }) => { 'g' : IDL.Func([list], [B, tree, stream], []), }); t.fill(IDL.Func([s], [], [])); + return IDL.Service({ 'f1' : IDL.Func( [list, IDL.Vec(IDL.Nat8), IDL.Opt(IDL.Bool)], @@ -129,4 +255,5 @@ export const idlFactory = ({ IDL }) => { 'bbbbb' : IDL.Func([b], [], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 8dd8d9bf9..13381c772 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 6b800e6cb..c7c105244 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 f1(&self, arg0: &List, arg1: &serde_bytes::ByteBuf, arg2: &Option) -> Result<()> { - ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await + pub async fn f1(&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 ec5bad50f..bbd788e03 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts +++ b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts @@ -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 7855c696a..1f5e2a585 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.did +++ b/rust/candid_parser/tests/assets/ok/fieldnat.did @@ -1,7 +1,7 @@ type tuple = record { text; text }; type non_tuple = record { 1 : text; 2 : text }; service : { - bab : (int, nat) -> (); + bab : (two : int, nat) -> (); bar : (record { "2" : int }) -> (variant { e20; e30 }); bas : (record { int; int }) -> (record { text; nat }); baz : (record { 2 : int; "2" : nat }) -> (record {}); diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.js b/rust/candid_parser/tests/assets/ok/fieldnat.js index 590e67cdd..f0154adec 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.js +++ b/rust/candid_parser/tests/assets/ok/fieldnat.js @@ -1,6 +1,40 @@ +import { IDL } from '@dfinity/candid'; + +export const tuple = IDL.Tuple(IDL.Text, IDL.Text); +export const non_tuple = IDL.Record({ _1_ : IDL.Text, _2_ : IDL.Text }); + +export const idlService = IDL.Service({ + 'bab' : IDL.Func([IDL.Int, IDL.Nat], [], []), + 'bar' : IDL.Func( + [IDL.Record({ '2' : IDL.Int })], + [IDL.Variant({ 'e20' : IDL.Null, 'e30' : IDL.Null })], + [], + ), + 'bas' : IDL.Func( + [IDL.Tuple(IDL.Int, IDL.Int)], + [IDL.Tuple(IDL.Text, IDL.Nat)], + [], + ), + 'baz' : IDL.Func( + [IDL.Record({ _2_ : IDL.Int, '2' : IDL.Nat })], + [IDL.Record({})], + [], + ), + 'bba' : IDL.Func([tuple], [non_tuple], []), + 'bib' : IDL.Func([IDL.Tuple(IDL.Int)], [IDL.Variant({ _0_ : IDL.Int })], []), + 'foo' : IDL.Func( + [IDL.Record({ _2_ : IDL.Int })], + [IDL.Record({ _2_ : IDL.Int, '_2' : IDL.Int })], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const tuple = IDL.Tuple(IDL.Text, IDL.Text); const non_tuple = IDL.Record({ _1_ : IDL.Text, _2_ : IDL.Text }); + return IDL.Service({ 'bab' : IDL.Func([IDL.Int, IDL.Nat], [], []), 'bar' : IDL.Func( @@ -31,4 +65,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.mo b/rust/candid_parser/tests/assets/ok/fieldnat.mo index 64f771834..f38c0359f 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.mo +++ b/rust/candid_parser/tests/assets/ok/fieldnat.mo @@ -5,7 +5,7 @@ module { public type non_tuple = { _1_ : Text; _2_ : Text }; public type tuple = (Text, Text); public type Self = actor { - bab : shared (Int, Nat) -> async (); + bab : shared (two : Int, Nat) -> async (); bar : shared { _50_ : Int } -> async { #e20; #e30 }; bas : shared ((Int, Int)) -> async ((Text, Nat)); baz : shared { _2_ : Int; _50_ : Nat } -> async {}; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index b5399f7f8..55a926045 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -17,7 +17,7 @@ pub struct BazArg { #[derive(CandidType, Deserialize)] pub struct BazRet {} #[derive(CandidType, Deserialize)] -pub struct Tuple (pub String,pub String,); +pub struct Tuple (pub String, pub String); #[derive(CandidType, Deserialize)] pub struct NonTuple { pub _1_: String, pub _2_: String } #[derive(CandidType, Deserialize)] @@ -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/float.d.ts b/rust/candid_parser/tests/assets/ok/float.d.ts index d30eecb3f..54cfd6a24 100644 --- a/rust/candid_parser/tests/assets/ok/float.d.ts +++ b/rust/candid_parser/tests/assets/ok/float.d.ts @@ -7,5 +7,7 @@ export interface _SERVICE { 'to_f32' : ActorMethod<[number], number>, 'to_f64' : ActorMethod<[number], number>, } +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/float.js b/rust/candid_parser/tests/assets/ok/float.js index f44891659..011e5700a 100644 --- a/rust/candid_parser/tests/assets/ok/float.js +++ b/rust/candid_parser/tests/assets/ok/float.js @@ -1,3 +1,13 @@ +import { IDL } from '@dfinity/candid'; + +export const idlService = IDL.Service({ + 'identity32' : IDL.Func([IDL.Float32], [IDL.Float32], []), + 'to_f32' : IDL.Func([IDL.Float64], [IDL.Float32], []), + 'to_f64' : IDL.Func([IDL.Float32], [IDL.Float64], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { return IDL.Service({ 'identity32' : IDL.Func([IDL.Float32], [IDL.Float32], []), @@ -5,4 +15,5 @@ export const idlFactory = ({ IDL }) => { 'to_f64' : IDL.Func([IDL.Float32], [IDL.Float64], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index cf3951e2c..f3f82eea0 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts +++ b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts @@ -23,5 +23,7 @@ export interface _SERVICE { 'high_order_fn_via_record' : ActorMethod<[R], bigint>, 'high_order_fn_via_record_inline' : ActorMethod<[RInline], bigint>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.js b/rust/candid_parser/tests/assets/ok/inline_methods.js index 1b63f3979..497784f13 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.js +++ b/rust/candid_parser/tests/assets/ok/inline_methods.js @@ -1,3 +1,34 @@ +import { IDL } from '@dfinity/candid'; + +export const Fn = IDL.Func([IDL.Nat], [IDL.Nat], ['query']); +export const Gn = Fn; +export const R = IDL.Record({ + 'x' : IDL.Nat, + 'fn' : Fn, + 'gn' : Gn, + 'nested' : IDL.Record({ 'fn' : Gn }), +}); +export const RInline = IDL.Record({ + 'x' : IDL.Nat, + 'fn' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), +}); + +export const idlService = IDL.Service({ + 'add_two' : IDL.Func([IDL.Nat], [IDL.Nat], []), + 'fn' : Fn, + 'high_order_fn' : IDL.Func([IDL.Nat, Fn], [IDL.Nat], []), + 'high_order_fn_inline' : IDL.Func( + [IDL.Nat, IDL.Func([IDL.Nat], [IDL.Nat], ['query'])], + [IDL.Nat], + [], + ), + 'high_order_fn_via_id' : IDL.Func([IDL.Nat, Gn], [Fn], []), + 'high_order_fn_via_record' : IDL.Func([R], [IDL.Nat], []), + 'high_order_fn_via_record_inline' : IDL.Func([RInline], [IDL.Nat], []), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const Fn = IDL.Func([IDL.Nat], [IDL.Nat], ['query']); const Gn = Fn; @@ -11,6 +42,7 @@ export const idlFactory = ({ IDL }) => { 'x' : IDL.Nat, 'fn' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), }); + return IDL.Service({ 'add_two' : IDL.Func([IDL.Nat], [IDL.Nat], []), 'fn' : Fn, @@ -25,4 +57,5 @@ export const idlFactory = ({ IDL }) => { 'high_order_fn_via_record_inline' : IDL.Func([RInline], [IDL.Nat], []), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.mo b/rust/candid_parser/tests/assets/ok/inline_methods.mo index b1c8b0168..f97f4ecc6 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.mo +++ b/rust/candid_parser/tests/assets/ok/inline_methods.mo @@ -3,9 +3,9 @@ module { public type Fn = shared query Nat -> async Nat; - public type Gn = Fn; public type R = { x : Nat; fn : Fn; gn : Gn; nested : { fn : Gn } }; public type RInline = { x : Nat; fn : shared query Nat -> async Nat }; + public type Gn = Fn; public type Self = actor { add_two : shared Nat -> async Nat; fn : Fn; diff --git a/rust/candid_parser/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 8e2c0aa6c..0df86b31e 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.d.ts +++ b/rust/candid_parser/tests/assets/ok/keyword.d.ts @@ -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 0a42d407c..9b1d2cec1 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.did +++ b/rust/candid_parser/tests/assets/ok/keyword.did @@ -6,7 +6,7 @@ type if = variant { leaf : int; }; type return = service { f : t; g : (list) -> (if, stream) }; -type t = func (return) -> (); +type t = func (server : return) -> (); type stream = opt record { head : nat; next : func () -> (stream) query }; service : { Oneway : () -> () oneway; diff --git a/rust/candid_parser/tests/assets/ok/keyword.js b/rust/candid_parser/tests/assets/ok/keyword.js index 032fe5f2a..a98f142c8 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.js +++ b/rust/candid_parser/tests/assets/ok/keyword.js @@ -1,3 +1,69 @@ +import { IDL } from '@dfinity/candid'; + +export const if_ = IDL.Rec(); +export const list = IDL.Rec(); +export const o = IDL.Rec(); +export const stream = IDL.Rec(); +export const t = IDL.Rec(); +o.fill(IDL.Opt(o)); +export const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list }); +list.fill(IDL.Opt(node)); +if_.fill( + IDL.Variant({ + 'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : if_, 'right' : if_ }), + 'leaf' : IDL.Int, + }) +); +stream.fill( + IDL.Opt( + IDL.Record({ 'head' : IDL.Nat, 'next' : IDL.Func([], [stream], ['query']) }) + ) +); +export const return_ = IDL.Service({ + 'f' : t, + 'g' : IDL.Func([list], [if_, stream], []), +}); +t.fill(IDL.Func([return_], [], [])); + +export const idlService = IDL.Service({ + 'Oneway' : IDL.Func([], [], ['oneway']), + 'f_' : IDL.Func([o], [o], []), + 'field' : IDL.Func( + [IDL.Record({ 'test' : IDL.Nat16, _1291438163_ : IDL.Nat8 })], + [IDL.Record({})], + [], + ), + 'fieldnat' : IDL.Func( + [IDL.Record({ _2_ : IDL.Int, '2' : IDL.Nat })], + [IDL.Tuple(IDL.Int)], + [], + ), + 'oneway' : IDL.Func([IDL.Nat8], [], ['oneway']), + 'oneway_' : IDL.Func([IDL.Nat8], [], ['oneway']), + 'query' : IDL.Func([IDL.Vec(IDL.Nat8)], [IDL.Vec(IDL.Nat8)], ['query']), + 'return' : IDL.Func([o], [o], []), + 'service' : t, + 'tuple' : IDL.Func( + [IDL.Tuple(IDL.Int, IDL.Vec(IDL.Nat8), IDL.Text)], + [IDL.Tuple(IDL.Int, IDL.Nat8)], + [], + ), + 'variant' : IDL.Func( + [ + IDL.Variant({ + 'A' : IDL.Null, + 'B' : IDL.Null, + 'C' : IDL.Null, + 'D' : IDL.Float64, + }), + ], + [], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const if_ = IDL.Rec(); const list = IDL.Rec(); @@ -26,6 +92,7 @@ export const idlFactory = ({ IDL }) => { 'g' : IDL.Func([list], [if_, stream], []), }); t.fill(IDL.Func([return_], [], [])); + return IDL.Service({ 'Oneway' : IDL.Func([], [], ['oneway']), 'f_' : IDL.Func([o], [o], []), @@ -63,4 +130,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo index 2802968e2..cf214b121 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.mo +++ b/rust/candid_parser/tests/assets/ok/keyword.mo @@ -2,16 +2,16 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type if_ = { #branch : { val : Int; left : if_; right : if_ }; #leaf : Int; }; + public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; public type list = ?node; - public type node = { head : Nat; tail : list }; public type o = ?o; - public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared 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 86e2a4c4e..086f853e4 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/malicious_doc.d.ts b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts index f613779f8..7491955ba 100644 --- a/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts @@ -22,5 +22,7 @@ export interface _SERVICE { */ 'get' : ActorMethod<[], MaliciousType>, } +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/malicious_doc.js b/rust/candid_parser/tests/assets/ok/malicious_doc.js index ff18f8ed1..552475ffe 100644 --- a/rust/candid_parser/tests/assets/ok/malicious_doc.js +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.js @@ -1,5 +1,17 @@ +import { IDL } from '@dfinity/candid'; + +export const MaliciousType = IDL.Record({ 'field' : IDL.Text }); + +export const idlService = IDL.Service({ + 'get' : IDL.Func([], [MaliciousType], ['query']), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const MaliciousType = IDL.Record({ 'field' : IDL.Text }); + return IDL.Service({ 'get' : IDL.Func([], [MaliciousType], ['query']) }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index 8cf54f90a..c49b43676 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -171,5 +171,7 @@ export interface _SERVICE { undefined >, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/management.js b/rust/candid_parser/tests/assets/ok/management.js index cb2b784b3..29817e94f 100644 --- a/rust/candid_parser/tests/assets/ok/management.js +++ b/rust/candid_parser/tests/assets/ok/management.js @@ -1,3 +1,235 @@ +import { IDL } from '@dfinity/candid'; + +export const bitcoin_network = IDL.Variant({ + 'mainnet' : IDL.Null, + 'testnet' : IDL.Null, +}); +export const bitcoin_address = IDL.Text; +export const get_balance_request = IDL.Record({ + 'network' : bitcoin_network, + 'address' : bitcoin_address, + 'min_confirmations' : IDL.Opt(IDL.Nat32), +}); +export const satoshi = IDL.Nat64; +export const get_current_fee_percentiles_request = IDL.Record({ + 'network' : bitcoin_network, +}); +export const millisatoshi_per_byte = IDL.Nat64; +export const get_utxos_request = IDL.Record({ + 'network' : bitcoin_network, + 'filter' : IDL.Opt( + IDL.Variant({ 'page' : IDL.Vec(IDL.Nat8), 'min_confirmations' : IDL.Nat32 }) + ), + 'address' : bitcoin_address, +}); +export const block_hash = IDL.Vec(IDL.Nat8); +export const outpoint = IDL.Record({ + 'txid' : IDL.Vec(IDL.Nat8), + 'vout' : IDL.Nat32, +}); +export const utxo = IDL.Record({ + 'height' : IDL.Nat32, + 'value' : satoshi, + 'outpoint' : outpoint, +}); +export const get_utxos_response = IDL.Record({ + 'next_page' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'tip_height' : IDL.Nat32, + 'tip_block_hash' : block_hash, + 'utxos' : IDL.Vec(utxo), +}); +export const send_transaction_request = IDL.Record({ + 'transaction' : IDL.Vec(IDL.Nat8), + 'network' : bitcoin_network, +}); +export const canister_id = IDL.Principal; +export const definite_canister_settings = IDL.Record({ + 'freezing_threshold' : IDL.Nat, + 'controllers' : IDL.Vec(IDL.Principal), + 'memory_allocation' : IDL.Nat, + 'compute_allocation' : IDL.Nat, +}); +export const canister_settings = IDL.Record({ + 'freezing_threshold' : IDL.Opt(IDL.Nat), + 'controllers' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'memory_allocation' : IDL.Opt(IDL.Nat), + 'compute_allocation' : IDL.Opt(IDL.Nat), +}); +export const ecdsa_curve = IDL.Variant({ 'secp256k1' : IDL.Null }); +export const http_header = IDL.Record({ + 'value' : IDL.Text, + 'name' : IDL.Text, +}); +export const http_response = IDL.Record({ + 'status' : IDL.Nat, + 'body' : IDL.Vec(IDL.Nat8), + 'headers' : IDL.Vec(http_header), +}); +export const wasm_module = IDL.Vec(IDL.Nat8); + +export const idlService = IDL.Service({ + 'bitcoin_get_balance' : IDL.Func([get_balance_request], [satoshi], []), + 'bitcoin_get_current_fee_percentiles' : IDL.Func( + [get_current_fee_percentiles_request], + [IDL.Vec(millisatoshi_per_byte)], + [], + ), + 'bitcoin_get_utxos' : IDL.Func([get_utxos_request], [get_utxos_response], []), + 'bitcoin_send_transaction' : IDL.Func([send_transaction_request], [], []), + 'canister_status' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [ + IDL.Record({ + 'status' : IDL.Variant({ + 'stopped' : IDL.Null, + 'stopping' : IDL.Null, + 'running' : IDL.Null, + }), + 'memory_size' : IDL.Nat, + 'cycles' : IDL.Nat, + 'settings' : definite_canister_settings, + 'idle_cycles_burned_per_day' : IDL.Nat, + 'module_hash' : IDL.Opt(IDL.Vec(IDL.Nat8)), + }), + ], + [], + ), + 'create_canister' : IDL.Func( + [IDL.Record({ 'settings' : IDL.Opt(canister_settings) })], + [IDL.Record({ 'canister_id' : canister_id })], + [], + ), + 'delete_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'deposit_cycles' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'ecdsa_public_key' : IDL.Func( + [ + IDL.Record({ + 'key_id' : IDL.Record({ 'name' : IDL.Text, 'curve' : ecdsa_curve }), + 'canister_id' : IDL.Opt(canister_id), + 'derivation_path' : IDL.Vec(IDL.Vec(IDL.Nat8)), + }), + ], + [ + IDL.Record({ + 'public_key' : IDL.Vec(IDL.Nat8), + 'chain_code' : IDL.Vec(IDL.Nat8), + }), + ], + [], + ), + 'http_request' : IDL.Func( + [ + IDL.Record({ + 'url' : IDL.Text, + 'method' : IDL.Variant({ + 'get' : IDL.Null, + 'head' : IDL.Null, + 'post' : IDL.Null, + }), + 'max_response_bytes' : IDL.Opt(IDL.Nat64), + 'body' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'transform' : IDL.Opt( + IDL.Record({ + 'function' : IDL.Func( + [ + IDL.Record({ + 'context' : IDL.Vec(IDL.Nat8), + 'response' : http_response, + }), + ], + [http_response], + ['query'], + ), + 'context' : IDL.Vec(IDL.Nat8), + }) + ), + 'headers' : IDL.Vec(http_header), + }), + ], + [http_response], + [], + ), + 'install_code' : IDL.Func( + [ + IDL.Record({ + 'arg' : IDL.Vec(IDL.Nat8), + 'wasm_module' : wasm_module, + 'mode' : IDL.Variant({ + 'reinstall' : IDL.Null, + 'upgrade' : IDL.Null, + 'install' : IDL.Null, + }), + 'canister_id' : canister_id, + }), + ], + [], + [], + ), + 'provisional_create_canister_with_cycles' : IDL.Func( + [ + IDL.Record({ + 'settings' : IDL.Opt(canister_settings), + 'specified_id' : IDL.Opt(canister_id), + 'amount' : IDL.Opt(IDL.Nat), + }), + ], + [IDL.Record({ 'canister_id' : canister_id })], + [], + ), + 'provisional_top_up_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id, 'amount' : IDL.Nat })], + [], + [], + ), + 'raw_rand' : IDL.Func([], [IDL.Vec(IDL.Nat8)], []), + 'sign_with_ecdsa' : IDL.Func( + [ + IDL.Record({ + 'key_id' : IDL.Record({ 'name' : IDL.Text, 'curve' : ecdsa_curve }), + 'derivation_path' : IDL.Vec(IDL.Vec(IDL.Nat8)), + 'message_hash' : IDL.Vec(IDL.Nat8), + }), + ], + [IDL.Record({ 'signature' : IDL.Vec(IDL.Nat8) })], + [], + ), + 'start_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'stop_canister' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'uninstall_code' : IDL.Func( + [IDL.Record({ 'canister_id' : canister_id })], + [], + [], + ), + 'update_settings' : IDL.Func( + [ + IDL.Record({ + 'canister_id' : IDL.Principal, + 'settings' : canister_settings, + }), + ], + [], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const bitcoin_network = IDL.Variant({ 'mainnet' : IDL.Null, @@ -65,6 +297,7 @@ export const idlFactory = ({ IDL }) => { 'headers' : IDL.Vec(http_header), }); const wasm_module = IDL.Vec(IDL.Nat8); + return IDL.Service({ 'bitcoin_get_balance' : IDL.Func([get_balance_request], [satoshi], []), 'bitcoin_get_current_fee_percentiles' : IDL.Func( @@ -230,4 +463,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 0609c7387..83fc90749 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -3,34 +3,41 @@ module { public type bitcoin_address = Text; - public type bitcoin_network = { #mainnet; #testnet }; - public type block_hash = Blob; - public type canister_id = Principal; + public type ecdsa_curve = { #secp256k1 }; + public type get_utxos_request = { + network : bitcoin_network; + filter : ?{ #page : Blob; #min_confirmations : Nat32 }; + address : bitcoin_address; + }; public type canister_settings = { freezing_threshold : ?Nat; controllers : ?[Principal]; memory_allocation : ?Nat; compute_allocation : ?Nat; }; - public type definite_canister_settings = { - freezing_threshold : Nat; - controllers : [Principal]; - memory_allocation : Nat; - compute_allocation : Nat; + public type user_id = Principal; + public type get_current_fee_percentiles_request = { + network : bitcoin_network; }; - public type ecdsa_curve = { #secp256k1 }; + public type outpoint = { txid : Blob; vout : Nat32 }; public type get_balance_request = { network : bitcoin_network; address : bitcoin_address; min_confirmations : ?Nat32; }; - public type get_current_fee_percentiles_request = { - network : bitcoin_network; + public type definite_canister_settings = { + freezing_threshold : Nat; + controllers : [Principal]; + memory_allocation : Nat; + compute_allocation : Nat; }; - public type get_utxos_request = { + public type satoshi = Nat64; + public type bitcoin_network = { #mainnet; #testnet }; + public type millisatoshi_per_byte = Nat64; + public type wasm_module = Blob; + public type send_transaction_request = { + transaction : Blob; network : bitcoin_network; - filter : ?{ #page : Blob; #min_confirmations : Nat32 }; - address : bitcoin_address; }; public type get_utxos_response = { next_page : ?Blob; @@ -38,22 +45,15 @@ module { tip_block_hash : block_hash; utxos : [utxo]; }; - public type http_header = { value : Text; name : Text }; + public type block_hash = Blob; + public type canister_id = Principal; + public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; public type http_response = { status : Nat; body : Blob; headers : [http_header]; }; - public type millisatoshi_per_byte = Nat64; - public type outpoint = { txid : Blob; vout : Nat32 }; - public type satoshi = Nat64; - public type send_transaction_request = { - transaction : Blob; - network : bitcoin_network; - }; - public type user_id = Principal; - public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; - public type wasm_module = Blob; + public type http_header = { value : Text; name : Text }; public type Self = actor { bitcoin_get_balance : shared get_balance_request -> async satoshi; bitcoin_get_current_fee_percentiles : shared get_current_fee_percentiles_request -> async [ diff --git a/rust/candid_parser/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 2f0a0f626..36368cbea 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursion.d.ts @@ -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 9137077e8..4dd70d2e7 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.did +++ b/rust/candid_parser/tests/assets/ok/recursion.did @@ -8,6 +8,6 @@ type tree = variant { }; // Doc comment for service id type s = service { f : t; g : (list) -> (B, tree, stream) }; -type t = func (s) -> (); +type t = func (server : s) -> (); type stream = opt record { head : nat; next : func () -> (stream) query }; service : s diff --git a/rust/candid_parser/tests/assets/ok/recursion.js b/rust/candid_parser/tests/assets/ok/recursion.js index d0fdaeee8..95d11d847 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.js +++ b/rust/candid_parser/tests/assets/ok/recursion.js @@ -1,3 +1,32 @@ +import { IDL } from '@dfinity/candid'; + +export const B = IDL.Rec(); +export const list = IDL.Rec(); +export const s = IDL.Rec(); +export const stream = IDL.Rec(); +export const tree = IDL.Rec(); +export const t = IDL.Func([s], [], []); +export const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list }); +list.fill(IDL.Opt(node)); +export const A = B; +B.fill(IDL.Opt(A)); +tree.fill( + IDL.Variant({ + 'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : tree, 'right' : tree }), + 'leaf' : IDL.Int, + }) +); +stream.fill( + IDL.Opt( + IDL.Record({ 'head' : IDL.Nat, 'next' : IDL.Func([], [stream], ['query']) }) + ) +); +s.fill(IDL.Service({ 'f' : t, 'g' : IDL.Func([list], [B, tree, stream], []) })); + +export const idlService = s.getType(); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const B = IDL.Rec(); const list = IDL.Rec(); @@ -26,6 +55,8 @@ export const idlFactory = ({ IDL }) => { s.fill( IDL.Service({ 'f' : t, 'g' : IDL.Func([list], [B, tree, stream], []) }) ); + return s.getType(); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo index cdb3eb519..34e72f02b 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.mo +++ b/rust/candid_parser/tests/assets/ok/recursion.mo @@ -2,17 +2,17 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type A = B; - public type B = ?A; - public type list = ?node; - public type node = { head : Nat; tail : list }; /// Doc comment for service id public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared 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 3b21c9c3e..239310042 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.rs +++ b/rust/candid_parser/tests/assets/ok/recursion.rs @@ -31,8 +31,8 @@ candid::define_service!(pub S : { pub struct Service(pub Principal); impl Service { - pub async fn f(&self, arg0: &S) -> Result<()> { - ic_cdk::call(self.0, "f", (arg0,)).await + pub async fn f(&self, server: &S) -> Result<()> { + ic_cdk::call(self.0, "f", (server,)).await } pub async fn g(&self, arg0: &List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts index 546910492..373f7d527 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts @@ -4,5 +4,7 @@ import type { IDL } from '@icp-sdk/core/candid'; export interface s { 'next' : ActorMethod<[], Principal> } export interface _SERVICE extends s {} +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.js b/rust/candid_parser/tests/assets/ok/recursive_class.js index a60a6487b..2d83bf276 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.js +++ b/rust/candid_parser/tests/assets/ok/recursive_class.js @@ -1,10 +1,22 @@ +import { IDL } from '@dfinity/candid'; + +export const s = IDL.Rec(); +s.fill(IDL.Service({ 'next' : IDL.Func([], [s], []) })); + +export const idlService = s.getType(); + +export const idlInitArgs = [s]; + export const idlFactory = ({ IDL }) => { const s = IDL.Rec(); s.fill(IDL.Service({ 'next' : IDL.Func([], [s], []) })); + return s.getType(); }; + export const init = ({ IDL }) => { const s = IDL.Rec(); s.fill(IDL.Service({ 'next' : IDL.Func([], [s], []) })); + return [s]; }; diff --git a/rust/candid_parser/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts index aea67cd85..b5b415749 100644 --- a/rust/candid_parser/tests/assets/ok/service.d.ts +++ b/rust/candid_parser/tests/assets/ok/service.d.ts @@ -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.did b/rust/candid_parser/tests/assets/ok/service.did index 7dc21afac..741e020cc 100644 --- a/rust/candid_parser/tests/assets/ok/service.did +++ b/rust/candid_parser/tests/assets/ok/service.did @@ -3,7 +3,7 @@ type Service2 = Service; type Func = func () -> (Service); service : { asArray : () -> (vec Service2, vec Func) query; - asPrincipal : () -> (Service2, Func); + asPrincipal : () -> (ret0 : Service2, ret1 : Func); asRecord : () -> (record { Service2; opt Service; Func }); asVariant : () -> (variant { a : Service2; b : record { f : opt Func } }); } diff --git a/rust/candid_parser/tests/assets/ok/service.js b/rust/candid_parser/tests/assets/ok/service.js index e7d8ffd37..9c937bc15 100644 --- a/rust/candid_parser/tests/assets/ok/service.js +++ b/rust/candid_parser/tests/assets/ok/service.js @@ -1,8 +1,34 @@ +import { IDL } from '@dfinity/candid'; + +export const Service = IDL.Rec(); +export const Func = IDL.Func([], [Service], []); +Service.fill(IDL.Service({ 'f' : Func })); +export const Service2 = Service; + +export const idlService = IDL.Service({ + 'asArray' : IDL.Func([], [IDL.Vec(Service2), IDL.Vec(Func)], ['query']), + 'asPrincipal' : IDL.Func([], [Service2, Func], []), + 'asRecord' : IDL.Func([], [IDL.Tuple(Service2, IDL.Opt(Service), Func)], []), + 'asVariant' : IDL.Func( + [], + [ + IDL.Variant({ + 'a' : Service2, + 'b' : IDL.Record({ 'f' : IDL.Opt(Func) }), + }), + ], + [], + ), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const Service = IDL.Rec(); const Func = IDL.Func([], [Service], []); Service.fill(IDL.Service({ 'f' : Func })); const Service2 = Service; + return IDL.Service({ 'asArray' : IDL.Func([], [IDL.Vec(Service2), IDL.Vec(Func)], ['query']), 'asPrincipal' : IDL.Func([], [Service2, Func], []), @@ -23,4 +49,5 @@ export const idlFactory = ({ IDL }) => { ), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/service.mo b/rust/candid_parser/tests/assets/ok/service.mo index 14ee15e7a..ff6c34865 100644 --- a/rust/candid_parser/tests/assets/ok/service.mo +++ b/rust/candid_parser/tests/assets/ok/service.mo @@ -2,8 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type Func = shared () -> async Service; public type Service = actor { f : Func }; + public type Func = shared () -> async Service; public type Service2 = Service; public type Self = actor { asArray : shared query () -> async ([Service2], [Func]); diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index cbcf60cd7..692b444b7 100644 --- a/rust/candid_parser/tests/assets/ok/service.rs +++ b/rust/candid_parser/tests/assets/ok/service.rs @@ -23,7 +23,7 @@ impl Service { pub async fn as_principal(&self) -> Result<(Service2,Func,)> { ic_cdk::call(self.0, "asPrincipal", ()).await } - pub async fn as_record(&self) -> Result<((Service2,Option,Func,),)> { + pub async fn as_record(&self) -> Result<((Service2, Option, Func),)> { ic_cdk::call(self.0, "asRecord", ()).await } pub async fn as_variant(&self) -> Result<(AsVariantRet,)> { diff --git a/rust/candid_parser/tests/assets/ok/unicode.d.ts b/rust/candid_parser/tests/assets/ok/unicode.d.ts index e9833d02e..6944f36d0 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.d.ts +++ b/rust/candid_parser/tests/assets/ok/unicode.d.ts @@ -18,5 +18,7 @@ export interface _SERVICE { '函数名' : ActorMethod<[A], B>, '👀' : ActorMethod<[bigint], bigint>, } +export declare const idlService: IDL.ServiceClass; +export declare const idlInitArgs: IDL.Type[]; export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/unicode.js b/rust/candid_parser/tests/assets/ok/unicode.js index 9d9774fb0..c97d46f52 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.js +++ b/rust/candid_parser/tests/assets/ok/unicode.js @@ -1,3 +1,27 @@ +import { IDL } from '@dfinity/candid'; + +export const A = IDL.Record({ + '\u{e000}' : IDL.Nat, + '📦🍦' : IDL.Nat, + '字段名' : IDL.Nat, + '字 段 名2' : IDL.Nat, +}); +export const B = IDL.Variant({ + '' : IDL.Null, + '空的' : IDL.Null, + ' 空的 ' : IDL.Null, + '1⃣️2⃣️3⃣️' : IDL.Null, +}); + +export const idlService = IDL.Service({ + '' : IDL.Func([IDL.Nat], [IDL.Nat], []), + '✈️ 🚗 ⛱️ ' : IDL.Func([], [], ['oneway']), + '函数名' : IDL.Func([A], [B], []), + '👀' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), +}); + +export const idlInitArgs = []; + export const idlFactory = ({ IDL }) => { const A = IDL.Record({ '\u{e000}' : IDL.Nat, @@ -11,6 +35,7 @@ export const idlFactory = ({ IDL }) => { ' 空的 ' : IDL.Null, '1⃣️2⃣️3⃣️' : IDL.Null, }); + return IDL.Service({ '' : IDL.Func([IDL.Nat], [IDL.Nat], []), '✈️ 🚗 ⛱️ ' : IDL.Func([], [], ['oneway']), @@ -18,4 +43,5 @@ export const idlFactory = ({ IDL }) => { '👀' : IDL.Func([IDL.Nat], [IDL.Nat], ['query']), }); }; + export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/service.did b/rust/candid_parser/tests/assets/service.did index cfec74e3f..43e2602bc 100644 --- a/rust/candid_parser/tests/assets/service.did +++ b/rust/candid_parser/tests/assets/service.did @@ -5,5 +5,5 @@ service : { asArray: () -> (vec Service2, vec Func) query; asRecord: () -> (record { Service2; opt Service; Func }); asVariant: () -> (variant { a: Service2; b: record { f:opt Func }}); - asPrincipal: () -> (Service2, Func); + asPrincipal: () -> (ret0 : Service2, ret1 : Func); } diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 4bf218ce7..ee6351366 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -7,6 +7,7 @@ use candid_parser::{ }; use goldenfile::Mint; use std::io::Write; +use std::panic::AssertUnwindSafe; use std::path::Path; #[test] @@ -134,7 +135,7 @@ fn compiler_test(resource: &str) { { match filename.file_name().unwrap().to_str().unwrap() { "unicode.did" | "escape.did" => check_error( - || motoko::compile(&env, &actor, &prog), + AssertUnwindSafe(|| motoko::compile(&env, &actor, &prog)), "not a valid Motoko id", ), _ => { diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index 085058f2e..80a022ccd 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -42,7 +42,16 @@ service : { let method = env.get_method(&actor, "f").unwrap(); { let args = parse_idl_args("(42,42,42,42)").unwrap(); - let encoded = args.to_bytes_with_types(&env, &method.args).unwrap(); + let encoded = args + .to_bytes_with_types( + &env, + &method + .args + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(), + ) + .unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!( decoded.args, @@ -57,10 +66,15 @@ service : { { let str = "(opt record { head = 1000; tail = opt record {head = -2000; tail = null}}, variant {a = 42})"; let args = parse_idl_args(str).unwrap(); - let encoded = args.to_bytes_with_types(&env, &method.rets).unwrap(); + let rets = method + .rets + .iter() + .map(|ret| ret.typ.clone()) + .collect::>(); + let encoded = args.to_bytes_with_types(&env, &rets).unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!(decoded.to_string(), "(\n opt record {\n 1_158_359_328 = 1_000 : int16;\n 1_291_237_008 = opt record {\n 1_158_359_328 = -2_000 : int16;\n 1_291_237_008 = null;\n };\n },\n variant { 97 = 42 : nat },\n)"); - let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &method.rets).unwrap(); + let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &rets).unwrap(); assert_eq!( decoded.to_string(), "(\n opt record {\n head = 1_000 : int16;\n tail = opt record { head = -2_000 : int16; tail = null };\n },\n variant { a = 42 : nat },\n)" diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 5495319ac..76e6416d1 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -131,7 +131,7 @@ impl TypeAnnotation { (Some(tys), None) => { let mut types = Vec::new(); for ty in tys.args.iter() { - types.push(ast_to_type(&env, ty)?); + types.push(ast_to_type(&env, &ty.typ)?); } Ok((env, types)) } @@ -139,11 +139,11 @@ impl TypeAnnotation { let actor = actor .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; let func = env.get_method(&actor, meth)?; - let types = match mode { - Mode::Encode => &func.args, - Mode::Decode => &func.rets, - } - .clone(); + let types_iter = match mode { + Mode::Encode => func.args.iter(), + Mode::Decode => func.rets.iter(), + }; + let types = types_iter.map(|arg| arg.typ.clone()).collect(); Ok((env, types)) } _ => unreachable!(),