From 7ebc82cb739c0ff19e79dfd1dffe80257debeae2 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:13:14 -0700 Subject: [PATCH 01/25] checkpoint --- rust/candid_parser/src/bindings/rust.rs | 187 +++++++++++++++--------- 1 file changed, 114 insertions(+), 73 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index ee1b1b8c..cd0c248f 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -1,4 +1,8 @@ use super::analysis::{chase_actor, infer_rec}; +use crate::{ + configs::{ConfigState, ConfigTree, Configs, StateElem}, + Deserialize, +}; use candid::pretty::utils::*; use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use convert_case::{Case, Casing}; @@ -12,19 +16,22 @@ pub enum Target { CanisterStub, } -#[derive(Clone)] +//#[derive(Clone)] pub struct Config { candid_crate: String, type_attributes: String, + tree: ConfigTree, canister_id: Option, service_name: String, target: Target, } impl Config { - pub fn new() -> Self { + pub fn new(configs: Configs) -> Self { + let tree = ConfigTree::from_configs("rust", configs).unwrap(); Config { candid_crate: "candid".to_string(), type_attributes: "".to_string(), + tree, canister_id: None, service_name: "service".to_string(), target: Target::CanisterCall, @@ -54,10 +61,28 @@ impl Config { self } } -impl Default for Config { - fn default() -> Self { - Self::new() +#[derive(Default, Deserialize, Clone)] +pub struct BindingConfig { + name: Option, + use_type: Option, + attributes: Option, + visibility: Option, +} +impl ConfigState for BindingConfig { + fn merge_config(&mut self, config: &Self, _is_recursive: bool) { + self.name = config.name.clone(); + self.use_type = config.use_type.clone(); + self.attributes = config.attributes.clone(); + if config.visibility.is_some() { + self.visibility = config.visibility.clone(); + } } + fn update_state(&mut self, _elem: &StateElem) {} + fn restore_state(&mut self, _elem: &StateElem) {} +} +pub struct State<'a> { + state: crate::configs::State<'a, BindingConfig>, + recs: RecPoints<'a>, } type RecPoints<'a> = BTreeSet<&'a str>; @@ -207,78 +232,90 @@ fn pp_variant_fields<'a>(fs: &'a [Field], recs: &RecPoints) -> RcDoc<'a> { let fields = concat(fs.iter().map(|f| pp_variant_field(f, recs)), ","); enclose_space("{", fields, "}") } - -fn pp_defs<'a>( - config: &'a Config, - env: &'a TypeEnv, - def_list: &'a [&'a str], - recs: &'a RecPoints, -) -> RcDoc<'a> { - let derive = if config.type_attributes.is_empty() { - "#[derive(CandidType, Deserialize)]" - } else { - &config.type_attributes - }; - lines(def_list.iter().map(|id| { - let ty = env.find_type(id).unwrap(); - let name = ident(id, Some(Case::Pascal)).append(" "); - let vis = "pub "; - match ty.as_ref() { - TypeInner::Record(fs) => { - let separator = if is_tuple(fs) { - RcDoc::text(";") - } else { - RcDoc::nil() - }; - str(derive) - .append(RcDoc::line()) - .append(vis) - .append("struct ") - .append(name) - .append(pp_record_fields(fs, recs, "pub")) - .append(separator) - .append(RcDoc::hardline()) - } - TypeInner::Variant(fs) => str(derive) - .append(RcDoc::line()) - .append(vis) - .append("enum ") - .append(name) - .append(pp_variant_fields(fs, recs)) - .append(RcDoc::hardline()), - TypeInner::Func(func) => str("candid::define_function!(") - .append(vis) - .append(name) - .append(": ") - .append(pp_ty_func(func)) - .append(");"), - TypeInner::Service(serv) => str("candid::define_service!(") - .append(vis) - .append(name) - .append(": ") - .append(pp_ty_service(serv)) - .append(");"), - _ => { - if recs.contains(id) { - str(derive) +impl<'a> State<'a> { + fn pp_defs(&mut self, def_list: &'a [&'a str]) -> RcDoc<'a> { + lines(def_list.iter().map(|id| { + let old = self.state.push_state(&StateElem::Label(id)); + let ty = self.state.env.find_type(id).unwrap(); + let name = self + .state + .config + .name + .clone() + .map(RcDoc::text) + .unwrap_or_else(|| ident(id, Some(Case::Pascal)).append(" ")); + let vis = self + .state + .config + .visibility + .clone() + .map(|v| RcDoc::text(v).append(" ")) + .unwrap_or(RcDoc::text("pub ")); + let derive = self + .state + .config + .attributes + .clone() + .map(RcDoc::text) + .unwrap_or(RcDoc::text("#[derive(CandidType, Deserialize)]")); + let res = match ty.as_ref() { + TypeInner::Record(fs) => { + let separator = if is_tuple(fs) { + RcDoc::text(";") + } else { + RcDoc::nil() + }; + derive .append(RcDoc::line()) .append(vis) .append("struct ") - .append(ident(id, Some(Case::Pascal))) - .append(enclose("(", pp_ty(ty, recs), ")")) - .append(";") - .append(RcDoc::hardline()) - } else { - str(vis) - .append(kwd("type")) .append(name) - .append("= ") - .append(pp_ty(ty, recs)) - .append(";") + .append(pp_record_fields(fs, &self.recs, "pub")) + .append(separator) + .append(RcDoc::hardline()) } - } - } - })) + TypeInner::Variant(fs) => derive + .append(RcDoc::line()) + .append(vis) + .append("enum ") + .append(name) + .append(pp_variant_fields(fs, &self.recs)) + .append(RcDoc::hardline()), + TypeInner::Func(func) => str("candid::define_function!(") + .append(vis) + .append(name) + .append(": ") + .append(pp_ty_func(func)) + .append(");"), + TypeInner::Service(serv) => str("candid::define_service!(") + .append(vis) + .append(name) + .append(": ") + .append(pp_ty_service(serv)) + .append(");"), + _ => { + if self.recs.contains(id) { + derive + .append(RcDoc::line()) + .append(vis) + .append("struct ") + .append(ident(id, Some(Case::Pascal))) + .append(enclose("(", pp_ty(ty, &self.recs), ")")) + .append(";") + .append(RcDoc::hardline()) + } else { + vis.append(kwd("type")) + .append(name) + .append("= ") + .append(pp_ty(ty, &self.recs)) + .append(";") + } + } + }; + self.state.pop_state(old, StateElem::Label(id)); + res + })) + } } fn pp_args(args: &[Type]) -> RcDoc { @@ -465,7 +502,11 @@ use {}::{{self, CandidType, Deserialize, Principal, Encode, Decode}}; env.0.iter().map(|pair| pair.0.as_ref()).collect() }; let recs = infer_rec(&env, &def_list).unwrap(); - let defs = pp_defs(config, &env, &def_list, &recs); + let mut state = State { + state: crate::configs::State::new(&config.tree, &env), + recs, + }; + let defs = state.pp_defs(&def_list); let doc = match &actor { None => defs, Some(actor) => { From 772b5f6e5198db4f24710fa778a5d8fb1844259a Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sat, 20 Apr 2024 12:06:40 -0700 Subject: [PATCH 02/25] checkpoint --- rust/candid_parser/src/bindings/rust.rs | 40 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index cd0c248f..a489ccc3 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -218,21 +218,33 @@ fn pp_record_fields<'a>(fs: &'a [Field], recs: &RecPoints, vis: &'a str) -> RcDo } } -fn pp_variant_field<'a>(field: &'a Field, recs: &RecPoints) -> RcDoc<'a> { - match field.ty.as_ref() { - TypeInner::Null => pp_label(&field.id, true, ""), - TypeInner::Record(fs) => { - pp_label(&field.id, true, "").append(pp_record_fields(fs, recs, "")) - } - _ => pp_label(&field.id, true, "").append(enclose("(", pp_ty(&field.ty, recs), ")")), +impl<'a> State<'a> { + fn pp_variant_field(&mut self, field: &'a Field) -> RcDoc<'a> { + let label = field.id.to_string(); + let old = self.state.push_state(&StateElem::Label(&label)); + let res = match field.ty.as_ref() { + TypeInner::Null => pp_label(&field.id, true, ""), + TypeInner::Record(fs) => { + pp_label(&field.id, true, "").append(pp_record_fields(fs, &self.recs, "")) + } + _ => pp_label(&field.id, true, "").append(enclose( + "(", + pp_ty(&field.ty, &self.recs), + ")", + )), + }; + self.state.pop_state(old, StateElem::Label(&label)); + res } -} -fn pp_variant_fields<'a>(fs: &'a [Field], recs: &RecPoints) -> RcDoc<'a> { - let fields = concat(fs.iter().map(|f| pp_variant_field(f, recs)), ","); - enclose_space("{", fields, "}") -} -impl<'a> State<'a> { + fn pp_variant_fields(&mut self, fs: &'a [Field]) -> RcDoc<'a> { + let old = self.state.push_state(&StateElem::Label("variant")); + let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); + let fields = concat(fields.into_iter(), ","); + let res = enclose_space("{", fields, "}"); + self.state.pop_state(old, StateElem::Label("variant")); + res + } fn pp_defs(&mut self, def_list: &'a [&'a str]) -> RcDoc<'a> { lines(def_list.iter().map(|id| { let old = self.state.push_state(&StateElem::Label(id)); @@ -279,7 +291,7 @@ impl<'a> State<'a> { .append(vis) .append("enum ") .append(name) - .append(pp_variant_fields(fs, &self.recs)) + .append(self.pp_variant_fields(fs)) .append(RcDoc::hardline()), TypeInner::Func(func) => str("candid::define_function!(") .append(vis) From 26c866dafa3d88105d8d03a142f3983be26af894 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sat, 20 Apr 2024 15:56:15 -0700 Subject: [PATCH 03/25] connect with didc and test --- rust/candid_parser/src/bindings/rust.rs | 16 ++++----- rust/candid_parser/tests/parse_type.rs | 4 ++- tools/didc/src/main.rs | 43 +++++++++++++------------ 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index a489ccc3..8606a95e 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -19,7 +19,6 @@ pub enum Target { //#[derive(Clone)] pub struct Config { candid_crate: String, - type_attributes: String, tree: ConfigTree, canister_id: Option, service_name: String, @@ -30,7 +29,6 @@ impl Config { let tree = ConfigTree::from_configs("rust", configs).unwrap(); Config { candid_crate: "candid".to_string(), - type_attributes: "".to_string(), tree, canister_id: None, service_name: "service".to_string(), @@ -41,11 +39,6 @@ impl Config { self.candid_crate = name; self } - /// Applies to all types for now - pub fn set_type_attributes(&mut self, attr: String) -> &mut Self { - self.type_attributes = attr; - self - } /// Only generates SERVICE struct if canister_id is not provided pub fn set_canister_id(&mut self, id: candid::Principal) -> &mut Self { self.canister_id = Some(id); @@ -222,6 +215,13 @@ impl<'a> State<'a> { fn pp_variant_field(&mut self, field: &'a Field) -> RcDoc<'a> { let label = field.id.to_string(); let old = self.state.push_state(&StateElem::Label(&label)); + let attr = self + .state + .config + .attributes + .clone() + .map(|a| RcDoc::text(a).append(RcDoc::line())) + .unwrap_or(RcDoc::nil()); let res = match field.ty.as_ref() { TypeInner::Null => pp_label(&field.id, true, ""), TypeInner::Record(fs) => { @@ -234,7 +234,7 @@ impl<'a> State<'a> { )), }; self.state.pop_state(old, StateElem::Label(&label)); - res + attr.append(res) } fn pp_variant_fields(&mut self, fs: &'a [Field]) -> RcDoc<'a> { diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 7dfb2fb6..04eae1f8 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -1,6 +1,7 @@ use candid::pretty::candid::compile; use candid::types::TypeEnv; use candid_parser::bindings::{javascript, motoko, rust, typescript}; +use candid_parser::configs::{ConfigTree, Configs}; use candid_parser::types::IDLProg; use candid_parser::typing::{check_file, check_prog}; use goldenfile::Mint; @@ -62,7 +63,8 @@ fn compiler_test(resource: &str) { } } { - let mut config = rust::Config::new(); + use std::str::FromStr; + let mut config = rust::Config::new(Configs::from_str("").unwrap()); config.set_canister_id(candid::Principal::from_text("aaaaa-aa").unwrap()); if filename.file_name().unwrap().to_str().unwrap() == "management.did" { config.set_target(rust::Target::Agent); diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index f770bf01..c5ee0b1a 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,6 +1,7 @@ use anyhow::{bail, Result}; use candid_parser::candid::types::{subtype, Type}; use candid_parser::{ + configs::Configs, parse_idl_args, parse_idl_value, pretty_check_file, pretty_parse, pretty_wrap, types::{IDLType, IDLTypes}, typing::ast_to_type, @@ -10,6 +11,7 @@ use clap::Parser; use std::collections::HashSet; use std::io; use std::path::PathBuf; +use std::str::FromStr; #[derive(Parser)] #[clap(version, author)] @@ -31,6 +33,9 @@ enum Command { #[clap(short, long, value_parser = ["js", "ts", "did", "mo", "rs", "rs-agent"])] /// Specifies target language target: String, + #[clap(short, long)] + /// Specifies binding generation config in TOML syntax + config: Option, }, /// Generate test suites for different languages Test { @@ -74,12 +79,9 @@ enum Command { Random { #[clap(flatten)] annotate: TypeAnnotation, - #[clap(short, long, conflicts_with("file"))] + #[clap(short, long)] /// Specifies random value generation config in TOML syntax config: Option, - #[clap(short, long)] - /// Load random value generation config from file - file: Option, #[clap(short, long, value_parser = ["did", "js"], default_value = "did")] /// Specifies target language lang: String, @@ -157,6 +159,13 @@ fn parse_args(str: &str) -> Result { fn parse_types(str: &str) -> Result { pretty_parse("type annotations", str) } +fn load_config(input: &Option) -> Result { + match input { + None => Configs::from_str(""), + Some(str) if str.ends_with(".toml") => Configs::from_str(&std::fs::read_to_string(str)?), + Some(str) => Configs::from_str(str), + } +} fn main() -> Result<()> { match Command::parse() { @@ -194,7 +203,12 @@ fn main() -> Result<()> { let ty2 = ast_to_type(&env, &ty2)?; subtype::subtype(&mut HashSet::new(), &env, &ty1, &ty2)?; } - Command::Bind { input, target } => { + Command::Bind { + input, + target, + config, + } => { + let configs = load_config(&config)?; let (env, actor) = pretty_check_file(&input)?; let content = match target.as_str() { "js" => candid_parser::bindings::javascript::compile(&env, &actor), @@ -202,11 +216,11 @@ fn main() -> Result<()> { "did" => candid_parser::pretty::candid::compile(&env, &actor), "mo" => candid_parser::bindings::motoko::compile(&env, &actor), "rs" => { - let config = candid_parser::bindings::rust::Config::new(); + let config = candid_parser::bindings::rust::Config::new(configs); candid_parser::bindings::rust::compile(&config, &env, &actor) } "rs-agent" => { - let mut config = candid_parser::bindings::rust::Config::new(); + let mut config = candid_parser::bindings::rust::Config::new(configs); config.set_target(candid_parser::bindings::rust::Target::Agent); candid_parser::bindings::rust::compile(&config, &env, &actor) } @@ -305,27 +319,16 @@ fn main() -> Result<()> { annotate, lang, config, - file, args, } => { - use candid_parser::configs::{Configs, Scope, ScopePos}; + use candid_parser::configs::{Scope, ScopePos}; use rand::Rng; - use std::str::FromStr; let (env, types) = if args.is_some() { annotate.get_types(Mode::Decode)? } else { annotate.get_types(Mode::Encode)? }; - let config = match (config, file) { - (None, None) => Configs::from_str("")?, - (Some(str), None) => Configs::from_str(&str)?, - (None, Some(file)) => { - let content = std::fs::read_to_string(&file) - .map_err(|_| Error::msg(format!("could not read {file}")))?; - Configs::from_str(&content)? - } - _ => unreachable!(), - }; + let config = load_config(&config)?; // TODO figure out how many bytes of entropy we need let seed: Vec = if let Some(ref args) = args { let (env, types) = annotate.get_types(Mode::Encode)?; From e73241f41ea98fe2f521c6ae1966cf565bc60eb6 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:25:37 -0700 Subject: [PATCH 04/25] checkpoint --- rust/candid_parser/src/bindings/rust.rs | 312 ++++++++++++------------ rust/candid_parser/tests/parse_type.rs | 2 +- 2 files changed, 156 insertions(+), 158 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 8606a95e..76186797 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -120,124 +120,119 @@ fn ident(id: &str, case: Option) -> RcDoc { ident_(id, case).0 } -fn pp_ty<'a>(ty: &'a Type, recs: &RecPoints) -> RcDoc<'a> { - use TypeInner::*; - match ty.as_ref() { - Null => str("()"), - Bool => str("bool"), - Nat => str("candid::Nat"), - Int => str("candid::Int"), - Nat8 => str("u8"), - Nat16 => str("u16"), - Nat32 => str("u32"), - Nat64 => str("u64"), - Int8 => str("i8"), - Int16 => str("i16"), - Int32 => str("i32"), - Int64 => str("i64"), - Float32 => str("f32"), - Float64 => str("f64"), - Text => str("String"), - Reserved => str("candid::Reserved"), - Empty => str("candid::Empty"), - Var(ref id) => { - let name = ident(id, Some(Case::Pascal)); - if recs.contains(id.as_str()) { - str("Box<").append(name).append(">") - } else { - name - } - } - Principal => str("Principal"), - Opt(ref t) => str("Option").append(enclose("<", pp_ty(t, recs), ">")), - // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), - Vec(ref t) => str("Vec").append(enclose("<", pp_ty(t, recs), ">")), - Record(ref fs) => pp_record_fields(fs, recs, ""), - Variant(_) => unreachable!(), // not possible after rewriting - Func(_) => unreachable!(), // not possible after rewriting - Service(_) => unreachable!(), // not possible after rewriting - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), - } -} - -fn pp_label<'a>(id: &'a SharedLabel, is_variant: bool, vis: &'a str) -> RcDoc<'a> { - let vis = if vis.is_empty() { - RcDoc::nil() - } else { - kwd(vis) - }; - match &**id { - Label::Named(id) => { - let case = if is_variant { Some(Case::Pascal) } else { None }; - let (doc, is_rename) = ident_(id, case); - if is_rename { - str("#[serde(rename=\"") - .append(id.escape_debug().to_string()) - .append("\")]") - .append(RcDoc::line()) - .append(vis) - .append(doc) - } else { - vis.append(doc) +impl<'a> State<'a> { + fn pp_ty<'b>(&mut self, ty: &'b Type, is_ref: bool) -> RcDoc<'b> { + use TypeInner::*; + match ty.as_ref() { + Null => str("()"), + Bool => str("bool"), + Nat => str("candid::Nat"), + Int => str("candid::Int"), + Nat8 => str("u8"), + Nat16 => str("u16"), + Nat32 => str("u32"), + Nat64 => str("u64"), + Int8 => str("i8"), + Int16 => str("i16"), + Int32 => str("i32"), + Int64 => str("i64"), + Float32 => str("f32"), + Float64 => str("f64"), + Text => str("String"), + Reserved => str("candid::Reserved"), + Empty => str("candid::Empty"), + Var(ref id) => { + let name = ident(id, Some(Case::Pascal)); + if !is_ref && self.recs.contains(id.as_str()) { + str("Box<").append(name).append(">") + } else { + name + } } + Principal => str("Principal"), + Opt(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), + // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` + Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), + Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), + Record(ref fs) => self.pp_record_fields(fs, ""), + Variant(_) => unreachable!(), // not possible after rewriting + Func(_) => unreachable!(), // not possible after rewriting + Service(_) => unreachable!(), // not possible after rewriting + Class(_, _) => unreachable!(), + Knot(_) | Unknown | Future => unreachable!(), } - Label::Id(n) | Label::Unnamed(n) => vis.append("_").append(RcDoc::as_string(n)).append("_"), } -} - -fn pp_record_field<'a>(field: &'a Field, recs: &RecPoints, vis: &'a str) -> RcDoc<'a> { - pp_label(&field.id, false, vis) - .append(kwd(":")) - .append(pp_ty(&field.ty, recs)) -} - -fn pp_record_fields<'a>(fs: &'a [Field], recs: &RecPoints, vis: &'a str) -> RcDoc<'a> { - if is_tuple(fs) { + fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, vis: &'b str) -> RcDoc<'b> { + let label = id.to_string(); + let old = self.state.push_state(&StateElem::Label(&label)); let vis = if vis.is_empty() { RcDoc::nil() } else { kwd(vis) }; - let tuple = RcDoc::concat( - fs.iter() - .map(|f| vis.clone().append(pp_ty(&f.ty, recs)).append(",")), - ); - enclose("(", tuple, ")") - } else { - let fields = concat(fs.iter().map(|f| pp_record_field(f, recs, vis)), ","); - enclose_space("{", fields, "}") - } -} - -impl<'a> State<'a> { - fn pp_variant_field(&mut self, field: &'a Field) -> RcDoc<'a> { - let label = field.id.to_string(); - let old = self.state.push_state(&StateElem::Label(&label)); - let attr = self - .state - .config - .attributes - .clone() - .map(|a| RcDoc::text(a).append(RcDoc::line())) - .unwrap_or(RcDoc::nil()); - let res = match field.ty.as_ref() { - TypeInner::Null => pp_label(&field.id, true, ""), - TypeInner::Record(fs) => { - pp_label(&field.id, true, "").append(pp_record_fields(fs, &self.recs, "")) + let res = match &**id { + Label::Named(id) => { + let case = if is_variant { Some(Case::Pascal) } else { None }; + let (doc, is_rename) = ident_(id, case); + if is_rename { + str("#[serde(rename=\"") + .append(id.escape_debug().to_string()) + .append("\")]") + .append(RcDoc::line()) + .append(vis) + .append(doc) + } else { + vis.append(doc) + } } - _ => pp_label(&field.id, true, "").append(enclose( + Label::Id(n) | Label::Unnamed(n) => { + vis.append("_").append(RcDoc::as_string(n)).append("_") + } + }; + self.state.pop_state(old, StateElem::Label(&label)); + res + } + fn pp_record_field<'b>(&mut self, field: &'b Field, vis: &'b str) -> RcDoc<'b> { + self.pp_label(&field.id, false, vis) + .append(kwd(":")) + .append(self.pp_ty(&field.ty, false)) + } + fn pp_record_fields<'b>(&mut self, fs: &'b [Field], vis: &'b str) -> RcDoc<'b> { + let old = self.state.push_state(&StateElem::Label("record")); + let res = if is_tuple(fs) { + let vis = if vis.is_empty() { + RcDoc::nil() + } else { + kwd(vis) + }; + let tuple = RcDoc::concat( + fs.iter() + .map(|f| vis.clone().append(self.pp_ty(&f.ty, false)).append(",")), + ); + enclose("(", tuple, ")") + } else { + let fields: Vec<_> = fs.iter().map(|f| self.pp_record_field(f, vis)).collect(); + let fields = concat(fields.into_iter(), ","); + enclose_space("{", fields, "}") + }; + self.state.pop_state(old, StateElem::Label("record")); + res + } + fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { + match field.ty.as_ref() { + TypeInner::Null => self.pp_label(&field.id, true, ""), + TypeInner::Record(fs) => self + .pp_label(&field.id, true, "") + .append(self.pp_record_fields(fs, "")), + _ => self.pp_label(&field.id, true, "").append(enclose( "(", - pp_ty(&field.ty, &self.recs), + self.pp_ty(&field.ty, false), ")", )), - }; - self.state.pop_state(old, StateElem::Label(&label)); - attr.append(res) + } } - fn pp_variant_fields(&mut self, fs: &'a [Field]) -> RcDoc<'a> { + fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::Label("variant")); let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); let fields = concat(fields.into_iter(), ","); @@ -282,7 +277,7 @@ impl<'a> State<'a> { .append(vis) .append("struct ") .append(name) - .append(pp_record_fields(fs, &self.recs, "pub")) + .append(self.pp_record_fields(fs, "pub")) .append(separator) .append(RcDoc::hardline()) } @@ -297,13 +292,13 @@ impl<'a> State<'a> { .append(vis) .append(name) .append(": ") - .append(pp_ty_func(func)) + .append(self.pp_ty_func(func)) .append(");"), TypeInner::Service(serv) => str("candid::define_service!(") .append(vis) .append(name) .append(": ") - .append(pp_ty_service(serv)) + .append(self.pp_ty_service(serv)) .append(");"), _ => { if self.recs.contains(id) { @@ -312,14 +307,14 @@ impl<'a> State<'a> { .append(vis) .append("struct ") .append(ident(id, Some(Case::Pascal))) - .append(enclose("(", pp_ty(ty, &self.recs), ")")) + .append(enclose("(", self.pp_ty(ty, false), ")")) .append(";") .append(RcDoc::hardline()) } else { vis.append(kwd("type")) .append(name) .append("= ") - .append(pp_ty(ty, &self.recs)) + .append(self.pp_ty(ty, false)) .append(";") } } @@ -328,70 +323,73 @@ impl<'a> State<'a> { res })) } -} - -fn pp_args(args: &[Type]) -> RcDoc { - let empty = RecPoints::default(); - let doc = concat(args.iter().map(|t| pp_ty(t, &empty)), ","); - enclose("(", doc, ")") -} -fn pp_ty_func(f: &Function) -> RcDoc { - let args = pp_args(&f.args); - let rets = pp_args(&f.rets); - let modes = candid::pretty::candid::pp_modes(&f.modes); - args.append(" ->") - .append(RcDoc::space()) - .append(rets.append(modes)) - .nest(INDENT_SPACE) -} -fn pp_ty_service(serv: &[(String, Type)]) -> RcDoc { - let doc = concat( - serv.iter().map(|(id, func)| { + fn pp_args<'b>(&mut self, args: &'b [Type]) -> RcDoc<'b> { + let doc: Vec<_> = args.iter().map(|t| self.pp_ty(t, true)).collect(); + let doc = concat(doc.into_iter(), ","); + enclose("(", doc, ")") + } + fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { + let args = self.pp_args(&f.args); + let rets = self.pp_args(&f.rets); + let modes = candid::pretty::candid::pp_modes(&f.modes); + args.append(" ->") + .append(RcDoc::space()) + .append(rets.append(modes)) + .nest(INDENT_SPACE) + } + fn pp_ty_service<'b>(&mut self, serv: &'b [(String, Type)]) -> RcDoc<'b> { + let mut list = Vec::new(); + for (id, func) in serv.iter() { let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => enclose("candid::func!(", pp_ty_func(f), ")"), - TypeInner::Var(_) => pp_ty(func, &RecPoints::default()).append("::ty()"), + TypeInner::Func(ref f) => enclose("candid::func!(", self.pp_ty_func(f), ")"), + TypeInner::Var(_) => self.pp_ty(func, true).append("::ty()"), _ => unreachable!(), }; - RcDoc::text("\"") - .append(id) - .append(kwd("\" :")) - .append(func_doc) - }), - ";", - ); - enclose_space("{", doc, "}") + list.push( + RcDoc::text("\"") + .append(id) + .append(kwd("\" :")) + .append(func_doc), + ); + } + let doc = concat(list.into_iter(), ";"); + enclose_space("{", doc, "}") + } } -fn pp_function<'a>(config: &Config, id: &'a str, func: &'a Function) -> RcDoc<'a> { +fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> RcDoc<'a> { + let env = TypeEnv::default(); + let mut state = State { + state: crate::configs::State::new(&config.tree, &env), + recs: RecPoints::default(), + }; let name = ident(id, Some(Case::Snake)); - let empty = BTreeSet::new(); let arg_prefix = str(match config.target { Target::CanisterCall => "&self", Target::Agent => "&self", Target::CanisterStub => unimplemented!(), }); - let args = concat( - std::iter::once(arg_prefix).chain( - func.args - .iter() - .enumerate() - .map(|(i, ty)| RcDoc::as_string(format!("arg{i}: ")).append(pp_ty(ty, &empty))), - ), - ",", - ); + let args: Vec<_> = func + .args + .iter() + .enumerate() + .map(|(i, ty)| RcDoc::as_string(format!("arg{i}: ")).append(state.pp_ty(ty, true))) + .collect(); + let args = concat(std::iter::once(arg_prefix).chain(args), ","); + let rets: Vec<_> = func + .rets + .iter() + .map(|ty| state.pp_ty(ty, true).append(",")) + .collect(); let rets = match config.target { - Target::CanisterCall => enclose( - "(", - RcDoc::concat(func.rets.iter().map(|ty| pp_ty(ty, &empty).append(","))), - ")", - ), + Target::CanisterCall => enclose("(", RcDoc::concat(rets), ")"), Target::Agent => match func.rets.len() { 0 => str("()"), - 1 => pp_ty(&func.rets[0], &empty), + 1 => state.pp_ty(&func.rets[0], true), _ => enclose( "(", RcDoc::intersperse( - func.rets.iter().map(|ty| pp_ty(ty, &empty)), + func.rets.iter().map(|ty| state.pp_ty(ty, true)), RcDoc::text(", "), ), ")", @@ -426,7 +424,7 @@ fn pp_function<'a>(config: &Config, id: &'a str, func: &'a Function) -> RcDoc<'a let rets = RcDoc::concat( func.rets .iter() - .map(|ty| str(", ").append(pp_ty(ty, &empty))), + .map(|ty| str(", ").append(state.pp_ty(ty, true))), ); str("let args = ").append(blob).append(RcDoc::hardline()) .append(format!("let bytes = self.1.{builder_method}(&self.0, \"{method}\").with_arg(args).{call}().await?;")) diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 04eae1f8..caba565d 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -1,7 +1,7 @@ use candid::pretty::candid::compile; use candid::types::TypeEnv; use candid_parser::bindings::{javascript, motoko, rust, typescript}; -use candid_parser::configs::{ConfigTree, Configs}; +use candid_parser::configs::Configs; use candid_parser::types::IDLProg; use candid_parser::typing::{check_file, check_prog}; use goldenfile::Mint; From d556ea00102a4320bd2492f9eb354cdb6b0447c5 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:38:59 -0700 Subject: [PATCH 05/25] vis --- rust/candid_parser/src/bindings/rust.rs | 71 ++++++++++++++++--------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 76186797..50d5ad9d 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -154,7 +154,7 @@ impl<'a> State<'a> { // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), - Record(ref fs) => self.pp_record_fields(fs, ""), + Record(ref fs) => self.pp_record_fields(fs, false), Variant(_) => unreachable!(), // not possible after rewriting Func(_) => unreachable!(), // not possible after rewriting Service(_) => unreachable!(), // not possible after rewriting @@ -162,13 +162,20 @@ impl<'a> State<'a> { Knot(_) | Unknown | Future => unreachable!(), } } - fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, vis: &'b str) -> RcDoc<'b> { + fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, need_vis: bool) -> RcDoc<'b> { let label = id.to_string(); let old = self.state.push_state(&StateElem::Label(&label)); - let vis = if vis.is_empty() { - RcDoc::nil() + let vis = if need_vis { + RcDoc::text( + self.state + .config + .visibility + .clone() + .unwrap_or("pub".to_string()), + ) + .append(" ") } else { - kwd(vis) + RcDoc::nil() }; let res = match &**id { Label::Named(id) => { @@ -192,26 +199,42 @@ impl<'a> State<'a> { self.state.pop_state(old, StateElem::Label(&label)); res } - fn pp_record_field<'b>(&mut self, field: &'b Field, vis: &'b str) -> RcDoc<'b> { - self.pp_label(&field.id, false, vis) + fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { + let tuple = fs.iter().enumerate().map(|(i, f)| { + let lab = i.to_string(); + let old = self.state.push_state(&StateElem::Label(&lab)); + let vis = if need_vis { + RcDoc::text( + self.state + .config + .visibility + .clone() + .unwrap_or("pub".to_string()), + ) + .append(" ") + } else { + RcDoc::nil() + }; + let res = vis.append(self.pp_ty(&f.ty, false)).append(","); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }); + enclose("(", RcDoc::concat(tuple), ")") + } + fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool) -> RcDoc<'b> { + self.pp_label(&field.id, false, need_vis) .append(kwd(":")) .append(self.pp_ty(&field.ty, false)) } - fn pp_record_fields<'b>(&mut self, fs: &'b [Field], vis: &'b str) -> RcDoc<'b> { + fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::Label("record")); let res = if is_tuple(fs) { - let vis = if vis.is_empty() { - RcDoc::nil() - } else { - kwd(vis) - }; - let tuple = RcDoc::concat( - fs.iter() - .map(|f| vis.clone().append(self.pp_ty(&f.ty, false)).append(",")), - ); - enclose("(", tuple, ")") + self.pp_tuple(fs, need_vis) } else { - let fields: Vec<_> = fs.iter().map(|f| self.pp_record_field(f, vis)).collect(); + let fields: Vec<_> = fs + .iter() + .map(|f| self.pp_record_field(f, need_vis)) + .collect(); let fields = concat(fields.into_iter(), ","); enclose_space("{", fields, "}") }; @@ -220,11 +243,11 @@ impl<'a> State<'a> { } fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { match field.ty.as_ref() { - TypeInner::Null => self.pp_label(&field.id, true, ""), + TypeInner::Null => self.pp_label(&field.id, true, false), TypeInner::Record(fs) => self - .pp_label(&field.id, true, "") - .append(self.pp_record_fields(fs, "")), - _ => self.pp_label(&field.id, true, "").append(enclose( + .pp_label(&field.id, true, false) + .append(self.pp_record_fields(fs, false)), + _ => self.pp_label(&field.id, true, false).append(enclose( "(", self.pp_ty(&field.ty, false), ")", @@ -277,7 +300,7 @@ impl<'a> State<'a> { .append(vis) .append("struct ") .append(name) - .append(self.pp_record_fields(fs, "pub")) + .append(self.pp_record_fields(fs, true)) .append(separator) .append(RcDoc::hardline()) } From e557da71f322d847414e37dad84e6c57bf79518f Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:27:01 -0700 Subject: [PATCH 06/25] vis --- rust/candid_parser/src/bindings/rust.rs | 62 ++++++++++++------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 50d5ad9d..c577c255 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -119,7 +119,13 @@ fn ident_(id: &str, case: Option) -> (RcDoc, bool) { fn ident(id: &str, case: Option) -> RcDoc { ident_(id, case).0 } - +fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { + match vis { + Some(vis) if vis.is_empty() => RcDoc::nil(), + Some(vis) => RcDoc::text(vis.clone()).append(" "), + None => RcDoc::text("pub "), + } +} impl<'a> State<'a> { fn pp_ty<'b>(&mut self, ty: &'b Type, is_ref: bool) -> RcDoc<'b> { use TypeInner::*; @@ -166,33 +172,37 @@ impl<'a> State<'a> { let label = id.to_string(); let old = self.state.push_state(&StateElem::Label(&label)); let vis = if need_vis { - RcDoc::text( - self.state - .config - .visibility - .clone() - .unwrap_or("pub".to_string()), - ) - .append(" ") + pp_vis(&self.state.config.visibility) } else { RcDoc::nil() }; + let attr = self + .state + .config + .attributes + .clone() + .map(|s| RcDoc::text(s).append(RcDoc::line())) + .unwrap_or(RcDoc::nil()); let res = match &**id { Label::Named(id) => { - let case = if is_variant { Some(Case::Pascal) } else { None }; - let (doc, is_rename) = ident_(id, case); - if is_rename { - str("#[serde(rename=\"") + let (doc, is_rename) = if let Some(name) = self.state.config.name.clone() { + (RcDoc::text(name), true) + } else { + let case = if is_variant { Some(Case::Pascal) } else { None }; + ident_(id, case) + }; + let attr = if is_rename { + attr.append("#[serde(rename=\"") .append(id.escape_debug().to_string()) .append("\")]") .append(RcDoc::line()) - .append(vis) - .append(doc) } else { - vis.append(doc) - } + attr + }; + attr.append(vis).append(doc) } Label::Id(n) | Label::Unnamed(n) => { + // TODO rename vis.append("_").append(RcDoc::as_string(n)).append("_") } }; @@ -204,14 +214,7 @@ impl<'a> State<'a> { let lab = i.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); let vis = if need_vis { - RcDoc::text( - self.state - .config - .visibility - .clone() - .unwrap_or("pub".to_string()), - ) - .append(" ") + pp_vis(&self.state.config.visibility) } else { RcDoc::nil() }; @@ -229,6 +232,7 @@ impl<'a> State<'a> { fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::Label("record")); let res = if is_tuple(fs) { + // TODO check if there is no name/attr in the label subtree self.pp_tuple(fs, need_vis) } else { let fields: Vec<_> = fs @@ -274,13 +278,7 @@ impl<'a> State<'a> { .clone() .map(RcDoc::text) .unwrap_or_else(|| ident(id, Some(Case::Pascal)).append(" ")); - let vis = self - .state - .config - .visibility - .clone() - .map(|v| RcDoc::text(v).append(" ")) - .unwrap_or(RcDoc::text("pub ")); + let vis = pp_vis(&self.state.config.visibility); let derive = self .state .config From dc719b64640f4928c04351eb282cd8cd9bb2639a Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:20:31 -0700 Subject: [PATCH 07/25] checkpoint --- rust/candid_parser/src/bindings/rust.rs | 80 ++++++++++++++----------- rust/candid_parser/src/configs.rs | 1 + 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index c577c255..25497c3f 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -129,44 +129,52 @@ fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { impl<'a> State<'a> { fn pp_ty<'b>(&mut self, ty: &'b Type, is_ref: bool) -> RcDoc<'b> { use TypeInner::*; - match ty.as_ref() { - Null => str("()"), - Bool => str("bool"), - Nat => str("candid::Nat"), - Int => str("candid::Int"), - Nat8 => str("u8"), - Nat16 => str("u16"), - Nat32 => str("u32"), - Nat64 => str("u64"), - Int8 => str("i8"), - Int16 => str("i16"), - Int32 => str("i32"), - Int64 => str("i64"), - Float32 => str("f32"), - Float64 => str("f64"), - Text => str("String"), - Reserved => str("candid::Reserved"), - Empty => str("candid::Empty"), - Var(ref id) => { - let name = ident(id, Some(Case::Pascal)); - if !is_ref && self.recs.contains(id.as_str()) { - str("Box<").append(name).append(">") - } else { - name + let elem = StateElem::Type(ty); + let old = self.state.push_state(&elem); + let res = if let Some(t) = self.state.config.use_type.clone() { + RcDoc::text(t) + } else { + match ty.as_ref() { + Null => str("()"), + Bool => str("bool"), + Nat => str("candid::Nat"), + Int => str("candid::Int"), + Nat8 => str("u8"), + Nat16 => str("u16"), + Nat32 => str("u32"), + Nat64 => str("u64"), + Int8 => str("i8"), + Int16 => str("i16"), + Int32 => str("i32"), + Int64 => str("i64"), + Float32 => str("f32"), + Float64 => str("f64"), + Text => str("String"), + Reserved => str("candid::Reserved"), + Empty => str("candid::Empty"), + Var(ref id) => { + let name = ident(id, Some(Case::Pascal)); + if !is_ref && self.recs.contains(id.as_str()) { + str("Box<").append(name).append(">") + } else { + name + } } + Principal => str("Principal"), + Opt(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), + // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` + Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), + Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), + Record(ref fs) => self.pp_record_fields(fs, false), + Variant(_) => unreachable!(), // not possible after rewriting + Func(_) => unreachable!(), // not possible after rewriting + Service(_) => unreachable!(), // not possible after rewriting + Class(_, _) => unreachable!(), + Knot(_) | Unknown | Future => unreachable!(), } - Principal => str("Principal"), - Opt(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), - // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), - Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), - Record(ref fs) => self.pp_record_fields(fs, false), - Variant(_) => unreachable!(), // not possible after rewriting - Func(_) => unreachable!(), // not possible after rewriting - Service(_) => unreachable!(), // not possible after rewriting - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), - } + }; + self.state.pop_state(old, elem); + res } fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, need_vis: bool) -> RcDoc<'b> { let label = id.to_string(); diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 729fc214..954fb472 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -222,6 +222,7 @@ pub fn path_name(t: &Type) -> String { TypeInner::Knot(id) => id.name, TypeInner::Principal => "principal", TypeInner::Opt(_) => "opt", + TypeInner::Vec(t) if matches!(t.as_ref(), TypeInner::Nat8) => "blob", TypeInner::Vec(_) => "vec", TypeInner::Record(_) => "record", TypeInner::Variant(_) => "variant", From 1d49e724428ad2a8c90a3fee3255834a15e26d84 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:35:57 -0700 Subject: [PATCH 08/25] fix merging semantics --- rust/candid_parser/src/bindings/rust.rs | 2 +- rust/candid_parser/src/configs.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 25497c3f..8770a558 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -54,7 +54,7 @@ impl Config { self } } -#[derive(Default, Deserialize, Clone)] +#[derive(Default, Deserialize, Clone, Debug)] pub struct BindingConfig { name: Option, use_type: Option, diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 954fb472..ba4d33f5 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -72,6 +72,8 @@ impl<'a, T: ConfigState> State<'a, T> { }; if let Some((state, is_recursive)) = new_state { self.config.merge_config(state, is_recursive); + } else { + self.config.merge_config(&T::default(), false); } old_config } @@ -82,7 +84,7 @@ impl<'a, T: ConfigState> State<'a, T> { } } -pub trait ConfigState: DeserializeOwned + Default + Clone { +pub trait ConfigState: DeserializeOwned + Default + Clone + std::fmt::Debug { fn merge_config(&mut self, config: &Self, is_recursive: bool); fn update_state(&mut self, elem: &StateElem); fn restore_state(&mut self, elem: &StateElem); From 6c421ada53d998f20be1a64009cba1534cf88bc1 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:18:44 -0700 Subject: [PATCH 09/25] checkpoint --- rust/candid_parser/src/bindings/rust.rs | 46 ++++++++++++++++++------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 8770a558..c815296a 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -266,7 +266,6 @@ impl<'a> State<'a> { )), } } - fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::Label("variant")); let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); @@ -285,7 +284,7 @@ impl<'a> State<'a> { .name .clone() .map(RcDoc::text) - .unwrap_or_else(|| ident(id, Some(Case::Pascal)).append(" ")); + .unwrap_or_else(|| ident(id, Some(Case::Pascal))); let vis = pp_vis(&self.state.config.visibility); let derive = self .state @@ -306,6 +305,7 @@ impl<'a> State<'a> { .append(vis) .append("struct ") .append(name) + .append(" ") .append(self.pp_record_fields(fs, true)) .append(separator) .append(RcDoc::hardline()) @@ -315,18 +315,19 @@ impl<'a> State<'a> { .append(vis) .append("enum ") .append(name) + .append(" ") .append(self.pp_variant_fields(fs)) .append(RcDoc::hardline()), TypeInner::Func(func) => str("candid::define_function!(") .append(vis) .append(name) - .append(": ") + .append(" : ") .append(self.pp_ty_func(func)) .append(");"), TypeInner::Service(serv) => str("candid::define_service!(") .append(vis) .append(name) - .append(": ") + .append(" : ") .append(self.pp_ty_service(serv)) .append(");"), _ => { @@ -335,14 +336,14 @@ impl<'a> State<'a> { .append(RcDoc::line()) .append(vis) .append("struct ") - .append(ident(id, Some(Case::Pascal))) + .append(name) .append(enclose("(", self.pp_ty(ty, false), ")")) .append(";") .append(RcDoc::hardline()) } else { vis.append(kwd("type")) .append(name) - .append("= ") + .append(" = ") .append(self.pp_ty(ty, false)) .append(";") } @@ -352,21 +353,38 @@ impl<'a> State<'a> { res })) } - fn pp_args<'b>(&mut self, args: &'b [Type]) -> RcDoc<'b> { - let doc: Vec<_> = args.iter().map(|t| self.pp_ty(t, true)).collect(); + fn pp_args<'b>(&mut self, args: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + let doc: Vec<_> = args + .iter() + .enumerate() + .map(|(i, t)| { + let lab = format!("{prefix}{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self.pp_ty(t, true); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }) + .collect(); let doc = concat(doc.into_iter(), ","); enclose("(", doc, ")") } fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { - let args = self.pp_args(&f.args); - let rets = self.pp_args(&f.rets); + let lab = StateElem::Label("func"); + let old = self.state.push_state(&lab); + let args = self.pp_args(&f.args, "arg"); + let rets = self.pp_args(&f.rets, "ret"); let modes = candid::pretty::candid::pp_modes(&f.modes); - args.append(" ->") + let res = args + .append(" ->") .append(RcDoc::space()) .append(rets.append(modes)) - .nest(INDENT_SPACE) + .nest(INDENT_SPACE); + self.state.pop_state(old, lab); + res } fn pp_ty_service<'b>(&mut self, serv: &'b [(String, Type)]) -> RcDoc<'b> { + let lab = StateElem::Label("service"); + let old = self.state.push_state(&lab); let mut list = Vec::new(); for (id, func) in serv.iter() { let func_doc = match func.as_ref() { @@ -382,7 +400,9 @@ impl<'a> State<'a> { ); } let doc = concat(list.into_iter(), ";"); - enclose_space("{", doc, "}") + let res = enclose_space("{", doc, "}"); + self.state.pop_state(old, lab); + res } } From 8a4c7f2730cd67e28409ef9bcd1ac82dbd6addf1 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:55:47 -0700 Subject: [PATCH 10/25] fix --- rust/candid_parser/src/bindings/rust.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index c815296a..b709c332 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -131,8 +131,8 @@ impl<'a> State<'a> { use TypeInner::*; let elem = StateElem::Type(ty); let old = self.state.push_state(&elem); - let res = if let Some(t) = self.state.config.use_type.clone() { - RcDoc::text(t) + let res = if let Some(t) = &self.state.config.use_type { + RcDoc::text(t.clone()) } else { match ty.as_ref() { Null => str("()"), @@ -153,7 +153,11 @@ impl<'a> State<'a> { Reserved => str("candid::Reserved"), Empty => str("candid::Empty"), Var(ref id) => { - let name = ident(id, Some(Case::Pascal)); + let name = if let Some(name) = &self.state.config.name { + RcDoc::text(name.clone()) + } else { + ident(id, Some(Case::Pascal)) + }; if !is_ref && self.recs.contains(id.as_str()) { str("Box<").append(name).append(">") } else { @@ -193,8 +197,8 @@ impl<'a> State<'a> { .unwrap_or(RcDoc::nil()); let res = match &**id { Label::Named(id) => { - let (doc, is_rename) = if let Some(name) = self.state.config.name.clone() { - (RcDoc::text(name), true) + let (doc, is_rename) = if let Some(name) = &self.state.config.name { + (RcDoc::text(name.clone()), true) } else { let case = if is_variant { Some(Case::Pascal) } else { None }; ident_(id, case) From 091e9b36e294f18eeda6a28e08c535c96a463d67 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:52:35 -0700 Subject: [PATCH 11/25] fix --- rust/candid_parser/src/bindings/rust.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index b709c332..2bb82d95 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -279,8 +279,13 @@ impl<'a> State<'a> { res } fn pp_defs(&mut self, def_list: &'a [&'a str]) -> RcDoc<'a> { - lines(def_list.iter().map(|id| { + let mut res = Vec::with_capacity(def_list.len()); + 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 name = self .state @@ -297,7 +302,7 @@ impl<'a> State<'a> { .clone() .map(RcDoc::text) .unwrap_or(RcDoc::text("#[derive(CandidType, Deserialize)]")); - let res = match ty.as_ref() { + let line = match ty.as_ref() { TypeInner::Record(fs) => { let separator = if is_tuple(fs) { RcDoc::text(";") @@ -354,8 +359,9 @@ impl<'a> State<'a> { } }; self.state.pop_state(old, StateElem::Label(id)); - res - })) + res.push(line) + } + lines(res.into_iter()) } fn pp_args<'b>(&mut self, args: &'b [Type], prefix: &'b str) -> RcDoc<'b> { let doc: Vec<_> = args From 44efb91095cf78ec59edfbe43efa7dc9d03abe99 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:57:33 -0700 Subject: [PATCH 12/25] fix root config fallback --- rust/candid_parser/src/configs.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index ba4d33f5..7efe0923 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -58,6 +58,13 @@ impl<'a, T: ConfigState> State<'a, T> { } } } + fn get_root_config(&self) -> Option<&'a T> { + if let Some(subtree) = self.open_tree { + subtree.state.as_ref().or_else(|| self.tree.state.as_ref()) + } else { + self.tree.state.as_ref() + } + } /// Update config based on the new elem in the path. Return the old state AFTER `update_state`. pub fn push_state(&mut self, elem: &StateElem) -> T { self.config.update_state(elem); @@ -73,7 +80,10 @@ impl<'a, T: ConfigState> State<'a, T> { if let Some((state, is_recursive)) = new_state { self.config.merge_config(state, is_recursive); } else { - self.config.merge_config(&T::default(), false); + self.config = T::default(); + if let Some(state) = self.get_root_config() { + self.config.merge_config(state, false); + } } old_config } @@ -115,6 +125,7 @@ impl ConfigTree { } pub fn get_config(&self, path: &[String]) -> Option<(&T, bool)> { let len = path.len(); + assert!(len > 0); let start = len.saturating_sub(self.max_depth as usize); for i in (start..len).rev() { let (path, tail) = path.split_at(i); From f5a8f004517977be0bb346b27175478a4d1e656b Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:49:15 -0700 Subject: [PATCH 13/25] checkpoint --- rust/candid_parser/src/bindings/rust.rs | 25 +++++++++++++++---------- rust/candid_parser/src/configs.rs | 17 ++++++++++++----- rust/candid_parser/src/random.rs | 2 +- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 2bb82d95..a13b84db 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -181,8 +181,6 @@ impl<'a> State<'a> { res } fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, need_vis: bool) -> RcDoc<'b> { - let label = id.to_string(); - let old = self.state.push_state(&StateElem::Label(&label)); let vis = if need_vis { pp_vis(&self.state.config.visibility) } else { @@ -195,7 +193,7 @@ impl<'a> State<'a> { .clone() .map(|s| RcDoc::text(s).append(RcDoc::line())) .unwrap_or(RcDoc::nil()); - let res = match &**id { + match &**id { Label::Named(id) => { let (doc, is_rename) = if let Some(name) = &self.state.config.name { (RcDoc::text(name.clone()), true) @@ -217,9 +215,7 @@ impl<'a> State<'a> { // TODO rename vis.append("_").append(RcDoc::as_string(n)).append("_") } - }; - self.state.pop_state(old, StateElem::Label(&label)); - res + } } fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { let tuple = fs.iter().enumerate().map(|(i, f)| { @@ -237,9 +233,14 @@ impl<'a> State<'a> { enclose("(", RcDoc::concat(tuple), ")") } fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool) -> RcDoc<'b> { - self.pp_label(&field.id, false, need_vis) + let lab = field.id.to_string(); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self + .pp_label(&field.id, false, need_vis) .append(kwd(":")) - .append(self.pp_ty(&field.ty, false)) + .append(self.pp_ty(&field.ty, false)); + self.state.pop_state(old, StateElem::Label(&lab)); + res } fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::Label("record")); @@ -258,7 +259,9 @@ impl<'a> State<'a> { res } fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { - match field.ty.as_ref() { + let lab = field.id.to_string(); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = match field.ty.as_ref() { TypeInner::Null => self.pp_label(&field.id, true, false), TypeInner::Record(fs) => self .pp_label(&field.id, true, false) @@ -268,7 +271,9 @@ impl<'a> State<'a> { self.pp_ty(&field.ty, false), ")", )), - } + }; + self.state.pop_state(old, StateElem::Label(&lab)); + res } fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::Label("variant")); diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 7efe0923..8884edd2 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -58,13 +58,13 @@ impl<'a, T: ConfigState> State<'a, T> { } } } - fn get_root_config(&self) -> Option<&'a T> { + /*fn get_root_config(&self) -> Option<&'a T> { if let Some(subtree) = self.open_tree { subtree.state.as_ref().or_else(|| self.tree.state.as_ref()) } else { self.tree.state.as_ref() } - } + }*/ /// Update config based on the new elem in the path. Return the old state AFTER `update_state`. pub fn push_state(&mut self, elem: &StateElem) -> T { self.config.update_state(elem); @@ -79,11 +79,17 @@ impl<'a, T: ConfigState> State<'a, T> { }; if let Some((state, is_recursive)) = new_state { self.config.merge_config(state, is_recursive); + //eprintln!("match path: {:?}, state: {:?}", self.path, self.config); } else { - self.config = T::default(); - if let Some(state) = self.get_root_config() { + //eprintln!("path: {:?}", self.path); + // random needs to merge with all None configs (no op) + // bindgen needs to merge with root config? + self.config.merge_config(&T::default(), false); + /*if let Some(state) = self.get_root_config() { self.config.merge_config(state, false); - } + } else { + self.config.merge_config(&T::default(), false); + }*/ } old_config } @@ -95,6 +101,7 @@ impl<'a, T: ConfigState> State<'a, T> { } pub trait ConfigState: DeserializeOwned + Default + Clone + std::fmt::Debug { + // TODO some flags need to know the path/current item to decide what to do fn merge_config(&mut self, config: &Self, is_recursive: bool); fn update_state(&mut self, elem: &StateElem); fn restore_state(&mut self, elem: &StateElem); diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index e62989b8..26f5ddac 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -23,7 +23,7 @@ impl Default for GenConfig { fn default() -> Self { GenConfig { range: None, - text: Some("ascii".to_string()), + text: None, width: Some(10), value: None, depth: Some(10), From ebdfad4df3c342d96dbe4d1a51c0c3b3b2a80488 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:27:38 -0700 Subject: [PATCH 14/25] add unmatched config --- rust/candid_parser/src/configs.rs | 21 +++++---------------- rust/candid_parser/src/random.rs | 12 +++++++++++- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 8884edd2..d480d244 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -6,8 +6,8 @@ use toml::{Table, Value}; pub struct State<'a, T: ConfigState> { tree: &'a ConfigTree, - path: Vec, open_tree: Option<&'a ConfigTree>, + pub path: Vec, pub config: T, pub env: &'a TypeEnv, } @@ -58,13 +58,6 @@ impl<'a, T: ConfigState> State<'a, T> { } } } - /*fn get_root_config(&self) -> Option<&'a T> { - if let Some(subtree) = self.open_tree { - subtree.state.as_ref().or_else(|| self.tree.state.as_ref()) - } else { - self.tree.state.as_ref() - } - }*/ /// Update config based on the new elem in the path. Return the old state AFTER `update_state`. pub fn push_state(&mut self, elem: &StateElem) -> T { self.config.update_state(elem); @@ -82,14 +75,7 @@ impl<'a, T: ConfigState> State<'a, T> { //eprintln!("match path: {:?}, state: {:?}", self.path, self.config); } else { //eprintln!("path: {:?}", self.path); - // random needs to merge with all None configs (no op) - // bindgen needs to merge with root config? - self.config.merge_config(&T::default(), false); - /*if let Some(state) = self.get_root_config() { - self.config.merge_config(state, false); - } else { - self.config.merge_config(&T::default(), false); - }*/ + self.config.merge_config(&T::unmatched_config(), false); } old_config } @@ -105,6 +91,9 @@ pub trait ConfigState: DeserializeOwned + Default + Clone + std::fmt::Debug { fn merge_config(&mut self, config: &Self, is_recursive: bool); fn update_state(&mut self, elem: &StateElem); fn restore_state(&mut self, elem: &StateElem); + fn unmatched_config() -> Self { + Self::default() + } } #[derive(Debug)] pub struct ConfigTree { diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 26f5ddac..95a36826 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -23,7 +23,7 @@ impl Default for GenConfig { fn default() -> Self { GenConfig { range: None, - text: None, + text: Some("ascii".to_string()), width: Some(10), value: None, depth: Some(10), @@ -61,6 +61,16 @@ impl ConfigState for GenConfig { } } } + fn unmatched_config() -> Self { + GenConfig { + range: None, + text: None, + width: None, + value: None, + depth: None, + size: None, + } + } } pub struct RandState<'a>(State<'a, GenConfig>); From 88ca33225a78cb925bd28863bafa9acacb69577d Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:32:54 -0700 Subject: [PATCH 15/25] fix attr --- rust/candid_parser/src/bindings/rust.rs | 23 +++++++++++++++-------- rust/candid_parser/src/configs.rs | 24 ++++++++++++++++-------- rust/candid_parser/src/random.rs | 2 +- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index a13b84db..68e87b3b 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -62,10 +62,17 @@ pub struct BindingConfig { visibility: Option, } impl ConfigState for BindingConfig { - fn merge_config(&mut self, config: &Self, _is_recursive: bool) { + fn merge_config(&mut self, config: &Self, elem: Option<&StateElem>, _is_recursive: bool) { self.name = config.name.clone(); self.use_type = config.use_type.clone(); - self.attributes = config.attributes.clone(); + // matched attributes can survive across labels in order to apply attributes to all labels at this level + if matches!(elem, Some(StateElem::Label(_))) { + if let Some(attr) = &config.attributes { + self.attributes = Some(attr.clone()); + } + } else { + self.attributes = config.attributes.clone(); + } if config.visibility.is_some() { self.visibility = config.visibility.clone(); } @@ -243,7 +250,7 @@ impl<'a> State<'a> { res } fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { - let old = self.state.push_state(&StateElem::Label("record")); + let old = self.state.push_state(&StateElem::TypeStr("record")); let res = if is_tuple(fs) { // TODO check if there is no name/attr in the label subtree self.pp_tuple(fs, need_vis) @@ -255,7 +262,7 @@ impl<'a> State<'a> { let fields = concat(fields.into_iter(), ","); enclose_space("{", fields, "}") }; - self.state.pop_state(old, StateElem::Label("record")); + self.state.pop_state(old, StateElem::TypeStr("record")); res } fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { @@ -276,11 +283,11 @@ impl<'a> State<'a> { res } fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { - let old = self.state.push_state(&StateElem::Label("variant")); + let old = self.state.push_state(&StateElem::TypeStr("variant")); let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); let fields = concat(fields.into_iter(), ","); let res = enclose_space("{", fields, "}"); - self.state.pop_state(old, StateElem::Label("variant")); + self.state.pop_state(old, StateElem::TypeStr("variant")); res } fn pp_defs(&mut self, def_list: &'a [&'a str]) -> RcDoc<'a> { @@ -384,7 +391,7 @@ impl<'a> State<'a> { enclose("(", doc, ")") } fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { - let lab = StateElem::Label("func"); + let lab = StateElem::TypeStr("func"); let old = self.state.push_state(&lab); let args = self.pp_args(&f.args, "arg"); let rets = self.pp_args(&f.rets, "ret"); @@ -398,7 +405,7 @@ impl<'a> State<'a> { res } fn pp_ty_service<'b>(&mut self, serv: &'b [(String, Type)]) -> RcDoc<'b> { - let lab = StateElem::Label("service"); + let lab = StateElem::TypeStr("service"); let old = self.state.push_state(&lab); let mut list = Vec::new(); for (id, func) in serv.iter() { diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index d480d244..d658f092 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -7,18 +7,22 @@ use toml::{Table, Value}; pub struct State<'a, T: ConfigState> { tree: &'a ConfigTree, open_tree: Option<&'a ConfigTree>, - pub path: Vec, + path: Vec, pub config: T, pub env: &'a TypeEnv, } +#[derive(Debug)] pub enum StateElem<'a> { Type(&'a Type), + TypeStr(&'a str), Label(&'a str), } +#[derive(Debug)] pub struct Scope<'a> { pub method: &'a str, pub position: Option, } +#[derive(Debug)] pub enum ScopePos { Arg, Ret, @@ -28,7 +32,7 @@ impl<'a, T: ConfigState> State<'a, T> { pub fn new(tree: &'a ConfigTree, env: &'a TypeEnv) -> Self { let mut config = T::default(); if let Some(state) = &tree.state { - config.merge_config(state, false); + config.merge_config(state, None, false); } Self { tree, @@ -52,6 +56,9 @@ impl<'a, T: ConfigState> State<'a, T> { None => (), } self.open_tree = self.tree.with_prefix(&path).or(Some(tree)); + if let Some(state) = self.open_tree.unwrap().state.as_ref() { + self.config.merge_config(state, None, false); + } } None => self.open_tree = None, } @@ -71,11 +78,12 @@ impl<'a, T: ConfigState> State<'a, T> { self.tree.get_config(&self.path) }; if let Some((state, is_recursive)) = new_state { - self.config.merge_config(state, is_recursive); + self.config.merge_config(state, Some(elem), is_recursive); //eprintln!("match path: {:?}, state: {:?}", self.path, self.config); } else { - //eprintln!("path: {:?}", self.path); - self.config.merge_config(&T::unmatched_config(), false); + self.config + .merge_config(&T::unmatched_config(), Some(elem), false); + //eprintln!("path: {:?}, state: {:?}", self.path, self.config); } old_config } @@ -87,8 +95,7 @@ impl<'a, T: ConfigState> State<'a, T> { } pub trait ConfigState: DeserializeOwned + Default + Clone + std::fmt::Debug { - // TODO some flags need to know the path/current item to decide what to do - fn merge_config(&mut self, config: &Self, is_recursive: bool); + fn merge_config(&mut self, config: &Self, elem: Option<&StateElem>, is_recursive: bool); fn update_state(&mut self, elem: &StateElem); fn restore_state(&mut self, elem: &StateElem); fn unmatched_config() -> Self { @@ -155,6 +162,7 @@ impl<'a> std::fmt::Display for StateElem<'a> { match self { StateElem::Type(t) => write!(f, "{}", path_name(t)), StateElem::Label(l) => write!(f, "{}", l), + StateElem::TypeStr(s) => write!(f, "{}", s), } } } @@ -253,7 +261,7 @@ fn parse() { text: Option, } impl ConfigState for T { - fn merge_config(&mut self, config: &Self, is_recursive: bool) { + fn merge_config(&mut self, config: &Self, _elem: Option<&StateElem>, is_recursive: bool) { *self = config.clone(); if is_recursive { self.size = Some(0); diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 95a36826..56bca513 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -32,7 +32,7 @@ impl Default for GenConfig { } } impl ConfigState for GenConfig { - fn merge_config(&mut self, config: &Self, is_recursive: bool) { + fn merge_config(&mut self, config: &Self, _elem: Option<&StateElem>, is_recursive: bool) { self.range = config.range.or(self.range); if config.text.is_some() { self.text = config.text.clone(); From c78744c0ab1221d7ea86701b21d4a8e245d95d42 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Fri, 26 Apr 2024 22:14:38 -0700 Subject: [PATCH 16/25] handlebar --- Cargo.lock | 66 ++++++++ rust/candid_parser/Cargo.toml | 5 +- rust/candid_parser/src/bindings/rust.rs | 153 +++++++++++++++++- rust/candid_parser/src/bindings/rust_call.hbs | 21 +++ 4 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 rust/candid_parser/src/bindings/rust_call.hbs diff --git a/Cargo.lock b/Cargo.lock index dc81f332..c879abe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,7 @@ dependencies = [ "dialoguer", "fake", "goldenfile", + "handlebars", "hex", "lalrpop", "lalrpop-util", @@ -563,6 +564,20 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" +[[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -831,6 +846,51 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pest" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2 1.0.79", + "quote 1.0.35", + "syn 2.0.53", +] + +[[package]] +name = "pest_meta" +version = "2.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -1362,6 +1422,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index 06d8c87f..7089ca38 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -30,6 +30,7 @@ anyhow.workspace = true lalrpop-util = "0.20.0" logos = "0.13" convert_case = "0.6" +handlebars = "5.1" arbitrary = { workspace = true, optional = true } toml = { version = "0.8", default-features = false, features = ["parse"], optional = true } @@ -47,8 +48,8 @@ test-generator = "0.3.0" rand.workspace = true [features] -configs = ["toml"] -random = ["configs", "arbitrary", "fake", "rand", "num-traits", "serde"] +configs = ["dep:toml", "dep:serde"] +random = ["configs", "dep:arbitrary", "dep:fake", "dep:rand", "dep:num-traits"] assist = ["dep:dialoguer", "dep:console", "dep:ctrlc"] all = ["random", "assist"] diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 68e87b3b..4c234c2b 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -7,6 +7,7 @@ use candid::pretty::utils::*; use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; use convert_case::{Case, Casing}; use pretty::RcDoc; +use serde::Serialize; use std::collections::BTreeSet; #[derive(Clone)] @@ -427,7 +428,37 @@ impl<'a> State<'a> { res } } - +fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> Method { + let env = TypeEnv::default(); + let mut state = State { + state: crate::configs::State::new(&config.tree, &env), + recs: RecPoints::default(), + }; + let args: Vec<_> = func + .args + .iter() + .enumerate() + .map(|(i, ty)| (RcDoc::<()>::text(format!("arg{i}")), state.pp_ty(ty, true))) + .collect(); + let rets: Vec<_> = func.rets.iter().map(|ty| state.pp_ty(ty, true)).collect(); + Method { + name: id.to_string(), + args: args + .into_iter() + .map(|(id, t)| { + ( + id.pretty(LINE_WIDTH).to_string(), + t.pretty(LINE_WIDTH).to_string(), + ) + }) + .collect(), + rets: rets + .into_iter() + .map(|x| x.pretty(LINE_WIDTH).to_string()) + .collect(), + } +} +/* fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> RcDoc<'a> { let env = TypeEnv::default(); let mut state = State { @@ -506,7 +537,18 @@ fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> RcDoc }; sig.append(enclose_space("{", body, "}")) } - +*/ +fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> Vec { + // TODO trace to service before we figure out what canister means in Rust + let serv = env.as_service(actor).unwrap(); + let mut res = Vec::new(); + for (id, func) in serv.iter() { + let func = env.as_func(func).unwrap(); + res.push(pp_function(config, id, func)); + } + res +} +/* fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> RcDoc<'a> { // TODO trace to service before we figure out what canister means in Rust let serv = env.as_service(actor).unwrap(); @@ -559,8 +601,53 @@ fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> RcDoc< } else { res } +}*/ +#[derive(Serialize)] +pub struct Output { + candid_crate: String, + service_name: String, + canister_id: Option, + type_defs: String, + methods: Vec, +} +#[derive(Serialize)] +pub struct Method { + name: String, + args: Vec<(String, String)>, + rets: Vec, + //mode: String, } +pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { + let source = include_str!("rust_call.hbs"); + let hbs = get_hbs(); + let (env, actor) = nominalize_all(env, actor); + let def_list: Vec<_> = if let Some(actor) = &actor { + chase_actor(&env, actor).unwrap() + } else { + env.0.iter().map(|pair| pair.0.as_ref()).collect() + }; + let recs = infer_rec(&env, &def_list).unwrap(); + let mut state = State { + state: crate::configs::State::new(&config.tree, &env), + recs, + }; + let defs = state.pp_defs(&def_list); + let methods = if let Some(actor) = &actor { + pp_actor(config, &env, actor) + } else { + Vec::new() + }; + let data = Output { + candid_crate: config.candid_crate.clone(), + service_name: config.service_name.to_case(Case::Pascal), + canister_id: Some(crate::Principal::from_slice(&[1, 2, 3])), + type_defs: defs.pretty(LINE_WIDTH).to_string(), + methods, + }; + hbs.render_template(source, &data).unwrap() +} +/* pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { let header = format!( r#"// This is an experimental feature to generate Rust binding from Candid. @@ -598,7 +685,7 @@ use {}::{{self, CandidType, Deserialize, Principal, Encode, Decode}}; let doc = RcDoc::text(header).append(RcDoc::line()).append(doc); doc.pretty(LINE_WIDTH).to_string() } - +*/ pub enum TypePath { Id(String), Opt, @@ -787,3 +874,63 @@ fn nominalize_all(env: &TypeEnv, actor: &Option) -> (TypeEnv, Option .map(|ty| nominalize(&mut res, &mut vec![], ty)); (res, actor) } + +fn get_hbs() -> handlebars::Handlebars<'static> { + use handlebars::*; + let mut hbs = Handlebars::new(); + hbs.register_escape_fn(handlebars::no_escape); + //hbs.set_strict_mode(true); + hbs.register_helper( + "PascalCase", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(s.to_case(Case::Pascal).as_ref())?; + Ok(()) + }, + ), + ); + hbs.register_helper( + "snake_case", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(s.to_case(Case::Snake).as_ref())?; + Ok(()) + }, + ), + ); + hbs.register_helper( + "principal_slice", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + let id = crate::Principal::from_text(s).unwrap(); + let slice = id + .as_slice() + .iter() + .map(|b| b.to_string()) + .collect::>() + .join(", "); + out.write(slice.as_str())?; + Ok(()) + }, + ), + ); + hbs +} diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs new file mode 100644 index 00000000..fc6f5391 --- /dev/null +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -0,0 +1,21 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use {{candid_crate}}::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use ic_cdk::api::call::CallResult as Result; + +{{type_defs}} +{{#if methods}} +pub struct {{PascalCase service_name}}(pub Principal); +impl {{PascalCase service_name}} { + {{#each methods}} + pub async fn {{snake_case this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { + ic_cdk::call(self.0, "{{this.name}}", ({{#each this.args}}{{this.0}},{{/each}})).await + } + {{/each}} +} +{{#if canister_id}} +pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} +pub const {{service_name}} : {{service_name}} = {{service_name}}(CANISTER_ID); +{{/if}} +{{/if}} From 0d1acfadcf34c4f61c9d8d9b7a761ba955f2094c Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:11:32 -0700 Subject: [PATCH 17/25] update test, diff is only space and newline --- rust/candid_parser/Cargo.toml | 7 +- rust/candid_parser/src/bindings/rust.rs | 237 ++++-------------- .../candid_parser/src/bindings/rust_agent.hbs | 22 ++ rust/candid_parser/src/bindings/rust_call.hbs | 10 +- rust/candid_parser/src/error.rs | 2 - rust/candid_parser/src/lib.rs | 2 - 6 files changed, 84 insertions(+), 196 deletions(-) create mode 100644 rust/candid_parser/src/bindings/rust_agent.hbs diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index 7089ca38..718aa119 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -26,18 +26,18 @@ num-bigint.workspace = true pretty.workspace = true thiserror.workspace = true anyhow.workspace = true +serde.workspace = true lalrpop-util = "0.20.0" logos = "0.13" convert_case = "0.6" handlebars = "5.1" +toml = { version = "0.8", default-features = false, features = ["parse"] } arbitrary = { workspace = true, optional = true } -toml = { version = "0.8", default-features = false, features = ["parse"], optional = true } fake = { version = "2.4", optional = true } rand = { version = "0.8", optional = true } num-traits = { workspace = true, optional = true } -serde = { workspace = true, optional = true } dialoguer = { version = "0.11", default-features = false, features = ["editor", "completion"], optional = true } console = { version = "0.15", optional = true } ctrlc = { version = "3.4", optional = true } @@ -48,8 +48,7 @@ test-generator = "0.3.0" rand.workspace = true [features] -configs = ["dep:toml", "dep:serde"] -random = ["configs", "dep:arbitrary", "dep:fake", "dep:rand", "dep:num-traits"] +random = ["dep:arbitrary", "dep:fake", "dep:rand", "dep:num-traits"] assist = ["dep:dialoguer", "dep:console", "dep:ctrlc"] all = ["random", "assist"] diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 4c234c2b..4810b527 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -330,7 +330,6 @@ impl<'a> State<'a> { .append(" ") .append(self.pp_record_fields(fs, true)) .append(separator) - .append(RcDoc::hardline()) } TypeInner::Variant(fs) => derive .append(RcDoc::line()) @@ -338,8 +337,7 @@ impl<'a> State<'a> { .append("enum ") .append(name) .append(" ") - .append(self.pp_variant_fields(fs)) - .append(RcDoc::hardline()), + .append(self.pp_variant_fields(fs)), TypeInner::Func(func) => str("candid::define_function!(") .append(vis) .append(name) @@ -361,7 +359,6 @@ impl<'a> State<'a> { .append(name) .append(enclose("(", self.pp_ty(ty, false), ")")) .append(";") - .append(RcDoc::hardline()) } else { vis.append(kwd("type")) .append(name) @@ -441,6 +438,7 @@ fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> Metho .map(|(i, ty)| (RcDoc::<()>::text(format!("arg{i}")), state.pp_ty(ty, true))) .collect(); let rets: Vec<_> = func.rets.iter().map(|ty| state.pp_ty(ty, true)).collect(); + let mode = if func.is_query() { "query" } else { "update" }.to_string(); Method { name: id.to_string(), args: args @@ -456,88 +454,9 @@ fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> Metho .into_iter() .map(|x| x.pretty(LINE_WIDTH).to_string()) .collect(), + mode, } } -/* -fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> RcDoc<'a> { - let env = TypeEnv::default(); - let mut state = State { - state: crate::configs::State::new(&config.tree, &env), - recs: RecPoints::default(), - }; - let name = ident(id, Some(Case::Snake)); - let arg_prefix = str(match config.target { - Target::CanisterCall => "&self", - Target::Agent => "&self", - Target::CanisterStub => unimplemented!(), - }); - let args: Vec<_> = func - .args - .iter() - .enumerate() - .map(|(i, ty)| RcDoc::as_string(format!("arg{i}: ")).append(state.pp_ty(ty, true))) - .collect(); - let args = concat(std::iter::once(arg_prefix).chain(args), ","); - let rets: Vec<_> = func - .rets - .iter() - .map(|ty| state.pp_ty(ty, true).append(",")) - .collect(); - let rets = match config.target { - Target::CanisterCall => enclose("(", RcDoc::concat(rets), ")"), - Target::Agent => match func.rets.len() { - 0 => str("()"), - 1 => state.pp_ty(&func.rets[0], true), - _ => enclose( - "(", - RcDoc::intersperse( - func.rets.iter().map(|ty| state.pp_ty(ty, true)), - RcDoc::text(", "), - ), - ")", - ), - }, - Target::CanisterStub => unimplemented!(), - }; - let sig = kwd("pub async fn") - .append(name) - .append(enclose("(", args, ")")) - .append(kwd(" ->")) - .append(enclose("Result<", rets, "> ")); - let method = id.escape_debug().to_string(); - let body = match config.target { - Target::CanisterCall => { - let args = RcDoc::concat((0..func.args.len()).map(|i| RcDoc::text(format!("arg{i},")))); - str("ic_cdk::call(self.0, \"") - .append(method) - .append("\", ") - .append(enclose("(", args, ")")) - .append(").await") - } - Target::Agent => { - let is_query = func.is_query(); - let builder_method = if is_query { "query" } else { "update" }; - let call = if is_query { "call" } else { "call_and_wait" }; - let args = RcDoc::intersperse( - (0..func.args.len()).map(|i| RcDoc::text(format!("&arg{i}"))), - RcDoc::text(", "), - ); - let blob = str("Encode!").append(enclose("(", args, ")?;")); - let rets = RcDoc::concat( - func.rets - .iter() - .map(|ty| str(", ").append(state.pp_ty(ty, true))), - ); - str("let args = ").append(blob).append(RcDoc::hardline()) - .append(format!("let bytes = self.1.{builder_method}(&self.0, \"{method}\").with_arg(args).{call}().await?;")) - .append(RcDoc::hardline()) - .append("Ok(Decode!(&bytes").append(rets).append(")?)") - } - Target::CanisterStub => unimplemented!(), - }; - sig.append(enclose_space("{", body, "}")) -} -*/ fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> Vec { // TODO trace to service before we figure out what canister means in Rust let serv = env.as_service(actor).unwrap(); @@ -548,60 +467,6 @@ fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> Vec(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> RcDoc<'a> { - // TODO trace to service before we figure out what canister means in Rust - let serv = env.as_service(actor).unwrap(); - let body = RcDoc::intersperse( - serv.iter().map(|(id, func)| { - let func = env.as_func(func).unwrap(); - pp_function(config, id, func) - }), - RcDoc::hardline(), - ); - let struct_name = config.service_name.to_case(Case::Pascal); - let service_def = match config.target { - Target::CanisterCall => format!("pub struct {}(pub Principal);", struct_name), - Target::Agent => format!( - "pub struct {}<'a>(pub Principal, pub &'a ic_agent::Agent);", - struct_name - ), - Target::CanisterStub => unimplemented!(), - }; - let service_impl = match config.target { - Target::CanisterCall => format!("impl {} ", struct_name), - Target::Agent => format!("impl<'a> {}<'a> ", struct_name), - Target::CanisterStub => unimplemented!(), - }; - let res = RcDoc::text(service_def) - .append(RcDoc::hardline()) - .append(service_impl) - .append(enclose_space("{", body, "}")) - .append(RcDoc::hardline()); - if let Some(cid) = config.canister_id { - let slice = cid - .as_slice() - .iter() - .map(|b| b.to_string()) - .collect::>() - .join(", "); - let id = RcDoc::text(format!( - "pub const CANISTER_ID : Principal = Principal::from_slice(&[{}]); // {}", - slice, cid - )); - let instance = match config.target { - Target::CanisterCall => format!( - "pub const {} : {} = {}(CANISTER_ID);", - config.service_name, struct_name, struct_name - ), - Target::Agent => "".to_string(), - Target::CanisterStub => unimplemented!(), - }; - res.append(id).append(RcDoc::hardline()).append(instance) - } else { - res - } -}*/ #[derive(Serialize)] pub struct Output { candid_crate: String, @@ -615,11 +480,15 @@ pub struct Method { name: String, args: Vec<(String, String)>, rets: Vec, - //mode: String, + mode: String, } pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { - let source = include_str!("rust_call.hbs"); + let source = match &config.target { + Target::CanisterCall => include_str!("rust_call.hbs"), + Target::Agent => include_str!("rust_agent.hbs"), + Target::CanisterStub => unimplemented!(), + }; let hbs = get_hbs(); let (env, actor) = nominalize_all(env, actor); let def_list: Vec<_> = if let Some(actor) = &actor { @@ -641,51 +510,12 @@ pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { let data = Output { candid_crate: config.candid_crate.clone(), service_name: config.service_name.to_case(Case::Pascal), - canister_id: Some(crate::Principal::from_slice(&[1, 2, 3])), + canister_id: config.canister_id, type_defs: defs.pretty(LINE_WIDTH).to_string(), methods, }; hbs.render_template(source, &data).unwrap() } -/* -pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { - let header = format!( - r#"// This is an experimental feature to generate Rust binding from Candid. -// You may want to manually adjust some of the types. -#![allow(dead_code, unused_imports)] -use {}::{{self, CandidType, Deserialize, Principal, Encode, Decode}}; -"#, - config.candid_crate - ); - let header = header - + match &config.target { - Target::CanisterCall => "use ic_cdk::api::call::CallResult as Result;\n", - Target::Agent => "type Result = std::result::Result;\n", - Target::CanisterStub => "", - }; - let (env, actor) = nominalize_all(env, actor); - let def_list: Vec<_> = if let Some(actor) = &actor { - chase_actor(&env, actor).unwrap() - } else { - env.0.iter().map(|pair| pair.0.as_ref()).collect() - }; - let recs = infer_rec(&env, &def_list).unwrap(); - let mut state = State { - state: crate::configs::State::new(&config.tree, &env), - recs, - }; - let defs = state.pp_defs(&def_list); - let doc = match &actor { - None => defs, - Some(actor) => { - let actor = pp_actor(config, &env, actor); - defs.append(actor) - } - }; - let doc = RcDoc::text(header).append(RcDoc::line()).append(doc); - doc.pretty(LINE_WIDTH).to_string() -} -*/ pub enum TypePath { Id(String), Opt, @@ -879,9 +709,9 @@ fn get_hbs() -> handlebars::Handlebars<'static> { use handlebars::*; let mut hbs = Handlebars::new(); hbs.register_escape_fn(handlebars::no_escape); - //hbs.set_strict_mode(true); + hbs.set_strict_mode(true); hbs.register_helper( - "PascalCase", + "escape_debug", Box::new( |h: &Helper, _: &Handlebars, @@ -890,7 +720,7 @@ fn get_hbs() -> handlebars::Handlebars<'static> { out: &mut dyn Output| -> HelperResult { let s = h.param(0).unwrap().value().as_str().unwrap(); - out.write(s.to_case(Case::Pascal).as_ref())?; + out.write(&s.escape_debug().to_string())?; Ok(()) }, ), @@ -910,6 +740,47 @@ fn get_hbs() -> handlebars::Handlebars<'static> { }, ), ); + hbs.register_helper( + "id", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(&ident(s, Some(Case::Snake)).pretty(LINE_WIDTH).to_string())?; + Ok(()) + }, + ), + ); + hbs.register_helper( + "vec_to_arity", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let vec: Vec<_> = h + .param(0) + .unwrap() + .value() + .as_array() + .unwrap() + .iter() + .map(|v| v.as_str().unwrap()) + .collect(); + match vec.len() { + 1 => out.write(vec[0])?, + _ => out.write(&format!("({})", vec.join(", ")))?, + } + Ok(()) + }, + ), + ); hbs.register_helper( "principal_slice", Box::new( diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs new file mode 100644 index 00000000..88007d73 --- /dev/null +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -0,0 +1,22 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use {{candid_crate}}::{self, CandidType, Deserialize, Principal, Encode, Decode}; +type Result = std::result::Result; + +{{type_defs}} +{{#if methods}} +pub struct {{service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); +impl<'a> {{service_name}}<'a> { + {{#each methods}} + pub async fn {{id this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { + let args = Encode!({{#each this.args}}&{{this.0}}{{#unless @last}},{{/unless}}{{/each}})?; + let bytes = self.1.{{this.mode}}(&self.0, "{{escape_debug this.name}}").with_arg(args).{{#if (eq this.mode "query")}}call{{else}}call_and_wait(){{/if}}.await?; + Ok(Decode!(&bytes{{#each this.rets}}, {{this}}{{/each}})?) + } + {{/each}} +} +{{#if canister_id}} +pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} +{{/if}} +{{/if}} diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs index fc6f5391..6cb7621c 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -6,16 +6,16 @@ use ic_cdk::api::call::CallResult as Result; {{type_defs}} {{#if methods}} -pub struct {{PascalCase service_name}}(pub Principal); -impl {{PascalCase service_name}} { +pub struct {{service_name}}(pub Principal); +impl {{service_name}} { {{#each methods}} - pub async fn {{snake_case this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { - ic_cdk::call(self.0, "{{this.name}}", ({{#each this.args}}{{this.0}},{{/each}})).await + pub async fn {{id this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { + ic_cdk::call(self.0, "{{escape_debug this.name}}", ({{#each this.args}}{{this.0}},{{/each}})).await } {{/each}} } {{#if canister_id}} pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} -pub const {{service_name}} : {{service_name}} = {{service_name}}(CANISTER_ID); +pub const {{snake_case service_name}} : {{service_name}} = {{service_name}}(CANISTER_ID); {{/if}} {{/if}} diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs index c88f4718..75b46e3e 100644 --- a/rust/candid_parser/src/error.rs +++ b/rust/candid_parser/src/error.rs @@ -99,8 +99,6 @@ impl From for Error { } } -#[cfg_attr(docsrs, doc(cfg(feature = "configs")))] -#[cfg(feature = "configs")] impl From for Error { fn from(e: toml::de::Error) -> Error { Error::msg(format!("toml error: {e}")) diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index dc034d5b..9853c3d0 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -136,8 +136,6 @@ pub use candid::*; #[cfg_attr(docsrs, doc(cfg(feature = "assist")))] #[cfg(feature = "assist")] pub mod assist; -#[cfg_attr(docsrs, doc(cfg(feature = "configs")))] -#[cfg(feature = "configs")] pub mod configs; #[cfg_attr(docsrs, doc(cfg(feature = "random")))] #[cfg(feature = "random")] From 7e41d1382b8e02903b80622806385d8a4be00c71 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:18:08 -0700 Subject: [PATCH 18/25] forget to commit test --- rust/candid_parser/tests/assets/ok/actor.rs | 1 + rust/candid_parser/tests/assets/ok/class.rs | 1 + rust/candid_parser/tests/assets/ok/comment.rs | 1 + rust/candid_parser/tests/assets/ok/cyclic.rs | 13 +-- rust/candid_parser/tests/assets/ok/empty.rs | 5 +- rust/candid_parser/tests/assets/ok/escape.rs | 1 + rust/candid_parser/tests/assets/ok/example.rs | 42 ++------- .../candid_parser/tests/assets/ok/fieldnat.rs | 15 +--- rust/candid_parser/tests/assets/ok/keyword.rs | 21 ++--- .../tests/assets/ok/management.rs | 87 +++---------------- .../tests/assets/ok/recursion.rs | 8 +- .../tests/assets/ok/recursive_class.rs | 2 + rust/candid_parser/tests/assets/ok/service.rs | 1 + rust/candid_parser/tests/assets/ok/unicode.rs | 8 +- 14 files changed, 45 insertions(+), 161 deletions(-) diff --git a/rust/candid_parser/tests/assets/ok/actor.rs b/rust/candid_parser/tests/assets/ok/actor.rs index 2f2dc6ad..79138a42 100644 --- a/rust/candid_parser/tests/assets/ok/actor.rs +++ b/rust/candid_parser/tests/assets/ok/actor.rs @@ -27,3 +27,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index 17a8df14..2198ab57 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -18,3 +18,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/comment.rs b/rust/candid_parser/tests/assets/ok/comment.rs index 4839a3f7..c819f2f1 100644 --- a/rust/candid_parser/tests/assets/ok/comment.rs +++ b/rust/candid_parser/tests/assets/ok/comment.rs @@ -6,3 +6,4 @@ use ic_cdk::api::call::CallResult as Result; pub type Id = u8; + diff --git a/rust/candid_parser/tests/assets/ok/cyclic.rs b/rust/candid_parser/tests/assets/ok/cyclic.rs index cb7c2821..f3fae3f3 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.rs +++ b/rust/candid_parser/tests/assets/ok/cyclic.rs @@ -8,23 +8,16 @@ pub type C = Box; pub type B = Option; #[derive(CandidType, Deserialize)] pub struct A(Option); - pub type Z = Box; pub type Y = Z; pub type X = Y; + pub struct Service(pub Principal); impl Service { - pub async fn f( - &self, - arg0: A, - arg1: B, - arg2: C, - arg3: X, - arg4: Y, - arg5: Z, - ) -> Result<()> { + pub async fn f(&self, arg0: A, arg1: B, arg2: C, arg3: X, arg4: Y, arg5: Z) -> Result<()> { ic_cdk::call(self.0, "f", (arg0,arg1,arg2,arg3,arg4,arg5,)).await } } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index ee1eed61..64d70472 100644 --- a/rust/candid_parser/tests/assets/ok/empty.rs +++ b/rust/candid_parser/tests/assets/ok/empty.rs @@ -6,16 +6,12 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct FArg {} - #[derive(CandidType, Deserialize)] pub enum FRet {} - #[derive(CandidType, Deserialize)] pub struct T (pub Box,); - #[derive(CandidType, Deserialize)] pub enum GRet { #[serde(rename="a")] A(Box) } - #[derive(CandidType, Deserialize)] pub enum HRet { #[serde(rename="a")] A(Box), #[serde(rename="b")] B{} } @@ -33,3 +29,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/escape.rs b/rust/candid_parser/tests/assets/ok/escape.rs index 6325fdc9..c6b66fef 100644 --- a/rust/candid_parser/tests/assets/ok/escape.rs +++ b/rust/candid_parser/tests/assets/ok/escape.rs @@ -24,3 +24,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index 180f01f6..6dec7032 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -6,17 +6,13 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct B (pub candid::Int,pub candid::Nat,); - #[derive(CandidType, Deserialize)] pub struct Node { pub head: candid::Nat, pub tail: Box } - #[derive(CandidType, Deserialize)] pub struct List(Option); - pub type A = Box; #[derive(CandidType, Deserialize)] pub struct B(Option); - #[derive(CandidType, Deserialize)] pub enum Tree { #[serde(rename="branch")] @@ -24,14 +20,11 @@ pub enum Tree { #[serde(rename="leaf")] Leaf(candid::Int), } - candid::define_function!(pub StreamInnerNext : () -> (Stream) query); #[derive(CandidType, Deserialize)] pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } - #[derive(CandidType, Deserialize)] pub struct Stream(Option); - candid::define_service!(pub S : { "f" : T::ty(); "g" : candid::func!((List) -> (B, Tree, Stream)); @@ -40,16 +33,12 @@ candid::define_function!(pub T : (S) -> ()); pub type MyType = Principal; #[derive(CandidType, Deserialize)] pub struct ListInner { pub head: candid::Int, pub tail: Box } - #[derive(CandidType, Deserialize)] pub struct List(Option); - #[derive(CandidType, Deserialize)] pub struct Nested3 { pub _0_: candid::Nat, pub _42_: candid::Nat, pub _43_: u8 } - #[derive(CandidType, Deserialize)] pub enum Nested41 { _42_, A, B, C } - #[derive(CandidType, Deserialize)] pub struct Nested { pub _0_: candid::Nat, @@ -60,7 +49,6 @@ pub struct Nested { pub _41_: Nested41, pub _42_: candid::Nat, } - candid::define_service!(pub BrokerFindRet : { "current" : candid::func!(() -> (u32)); "up" : candid::func!(() -> ()); @@ -70,13 +58,10 @@ candid::define_service!(pub Broker : { }); #[derive(CandidType, Deserialize)] pub enum HArg1 { A(candid::Nat), B(Option) } - #[derive(CandidType, Deserialize)] pub struct HRet42 {} - #[derive(CandidType, Deserialize)] pub struct HRet { pub _42_: HRet42, pub id: candid::Nat } - candid::define_function!(pub FArg1 : (i32) -> (i64)); candid::define_function!(pub F : (List, FArg1) -> (Option)); #[derive(CandidType, Deserialize)] @@ -90,30 +75,18 @@ impl Service { pub async fn f(&self, arg0: S) -> Result<()> { ic_cdk::call(self.0, "f", (arg0,)).await } - pub async fn f_1( - &self, - arg0: List, - arg1: serde_bytes::ByteBuf, - arg2: Option, - ) -> Result<()> { ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await } + pub async fn f_1(&self, arg0: List, arg1: serde_bytes::ByteBuf, arg2: Option) -> Result<()> { + ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await + } pub async fn g(&self, arg0: List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await } - pub async fn g_1( - &self, - arg0: MyType, - arg1: List, - arg2: Option, - arg3: Nested, - ) -> Result<(candid::Int,Broker,)> { + pub async fn g_1(&self, arg0: MyType, arg1: List, arg2: Option, arg3: Nested) -> Result<(candid::Int,Broker,)> { ic_cdk::call(self.0, "g1", (arg0,arg1,arg2,arg3,)).await } - pub async fn h( - &self, - arg0: Vec>, - arg1: HArg1, - arg2: Option, - ) -> Result<(HRet,)> { ic_cdk::call(self.0, "h", (arg0,arg1,arg2,)).await } + pub async fn h(&self, arg0: Vec>, arg1: HArg1, arg2: Option) -> Result<(HRet,)> { + ic_cdk::call(self.0, "h", (arg0,arg1,arg2,)).await + } pub async fn i(&self, arg0: List, arg1: FArg1) -> Result<(Option,)> { ic_cdk::call(self.0, "i", (arg0,arg1,)).await } @@ -123,3 +96,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index a42fec7c..8cbba81a 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -6,32 +6,24 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct BarArg { #[serde(rename="2")] pub _50_: candid::Int } - #[derive(CandidType, Deserialize)] pub enum BarRet { #[serde(rename="e20")] E20, #[serde(rename="e30")] E30 } - #[derive(CandidType, Deserialize)] pub struct BazArg { pub _2_: candid::Int, #[serde(rename="2")] pub _50_: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct BazRet {} - #[derive(CandidType, Deserialize)] pub struct Tuple (pub String,pub String,); - #[derive(CandidType, Deserialize)] pub struct NonTuple { pub _1_: String, pub _2_: String } - #[derive(CandidType, Deserialize)] pub enum BibRet { _0_(candid::Int) } - #[derive(CandidType, Deserialize)] pub struct FooArg { pub _2_: candid::Int } - #[derive(CandidType, Deserialize)] pub struct FooRet { pub _2_: candid::Int, pub _2: candid::Int } @@ -43,9 +35,9 @@ impl Service { pub async fn bar(&self, arg0: BarArg) -> Result<(BarRet,)> { ic_cdk::call(self.0, "bar", (arg0,)).await } - pub async fn bas(&self, arg0: (candid::Int,candid::Int,)) -> Result< - ((String,candid::Nat,),) - > { ic_cdk::call(self.0, "bas", (arg0,)).await } + 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,)> { ic_cdk::call(self.0, "baz", (arg0,)).await } @@ -61,3 +53,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index 0010e120..fd6ce914 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.rs +++ b/rust/candid_parser/tests/assets/ok/keyword.rs @@ -6,26 +6,20 @@ use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] pub struct O(Option>); - #[derive(CandidType, Deserialize)] pub struct FieldArg { pub test: u16, pub _1291438163_: u8 } - #[derive(CandidType, Deserialize)] pub struct FieldRet {} - #[derive(CandidType, Deserialize)] pub struct FieldnatArg { pub _2_: candid::Int, #[serde(rename="2")] pub _50_: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct Node { pub head: candid::Nat, pub tail: Box } - #[derive(CandidType, Deserialize)] pub struct List(Option); - #[derive(CandidType, Deserialize)] pub enum If { #[serde(rename="branch")] @@ -33,14 +27,11 @@ pub enum If { #[serde(rename="leaf")] Leaf(candid::Int), } - candid::define_function!(pub StreamInnerNext : () -> (Stream) query); #[derive(CandidType, Deserialize)] pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } - #[derive(CandidType, Deserialize)] pub struct Stream(Option); - candid::define_service!(pub Return : { "f" : T::ty(); "g" : candid::func!((List) -> (If, Stream)); @@ -69,19 +60,16 @@ impl Service { pub async fn oneway(&self, arg0: u8) -> Result<()> { ic_cdk::call(self.0, "oneway_", (arg0,)).await } - pub async fn query(&self, arg0: serde_bytes::ByteBuf) -> Result< - (serde_bytes::ByteBuf,) - > { ic_cdk::call(self.0, "query", (arg0,)).await } + pub async fn query(&self, arg0: serde_bytes::ByteBuf) -> Result<(serde_bytes::ByteBuf,)> { + ic_cdk::call(self.0, "query", (arg0,)).await + } 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 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<()> { @@ -90,3 +78,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index a74b2a2b..a3715bbe 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -11,7 +11,6 @@ pub enum BitcoinNetwork { #[serde(rename="testnet")] Testnet, } - pub type BitcoinAddress = String; #[derive(CandidType, Deserialize)] pub struct GetBalanceRequest { @@ -19,11 +18,9 @@ pub struct GetBalanceRequest { pub address: BitcoinAddress, pub min_confirmations: Option, } - pub type Satoshi = u64; #[derive(CandidType, Deserialize)] pub struct GetCurrentFeePercentilesRequest { pub network: BitcoinNetwork } - pub type MillisatoshiPerByte = u64; #[derive(CandidType, Deserialize)] pub enum GetUtxosRequestFilterInner { @@ -32,21 +29,17 @@ pub enum GetUtxosRequestFilterInner { #[serde(rename="min_confirmations")] MinConfirmations(u32), } - #[derive(CandidType, Deserialize)] pub struct GetUtxosRequest { pub network: BitcoinNetwork, pub filter: Option, pub address: BitcoinAddress, } - pub type BlockHash = serde_bytes::ByteBuf; #[derive(CandidType, Deserialize)] pub struct Outpoint { pub txid: serde_bytes::ByteBuf, pub vout: u32 } - #[derive(CandidType, Deserialize)] pub struct Utxo { pub height: u32, pub value: Satoshi, pub outpoint: Outpoint } - #[derive(CandidType, Deserialize)] pub struct GetUtxosResponse { pub next_page: Option, @@ -54,17 +47,14 @@ pub struct GetUtxosResponse { pub tip_block_hash: BlockHash, pub utxos: Vec, } - #[derive(CandidType, Deserialize)] pub struct SendTransactionRequest { pub transaction: serde_bytes::ByteBuf, pub network: BitcoinNetwork, } - pub type CanisterId = Principal; #[derive(CandidType, Deserialize)] pub struct CanisterStatusArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub enum CanisterStatusRetStatus { #[serde(rename="stopped")] @@ -74,7 +64,6 @@ pub enum CanisterStatusRetStatus { #[serde(rename="running")] Running, } - #[derive(CandidType, Deserialize)] pub struct DefiniteCanisterSettings { pub freezing_threshold: candid::Nat, @@ -82,7 +71,6 @@ pub struct DefiniteCanisterSettings { pub memory_allocation: candid::Nat, pub compute_allocation: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct CanisterStatusRet { pub status: CanisterStatusRetStatus, @@ -92,7 +80,6 @@ pub struct CanisterStatusRet { pub idle_cycles_burned_per_day: candid::Nat, pub module_hash: Option, } - #[derive(CandidType, Deserialize)] pub struct CanisterSettings { pub freezing_threshold: Option, @@ -100,38 +87,29 @@ pub struct CanisterSettings { pub memory_allocation: Option, pub compute_allocation: Option, } - #[derive(CandidType, Deserialize)] pub struct CreateCanisterArg { pub settings: Option } - #[derive(CandidType, Deserialize)] pub struct CreateCanisterRet { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct DeleteCanisterArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct DepositCyclesArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub enum EcdsaCurve { #[serde(rename="secp256k1")] Secp256K1 } - #[derive(CandidType, Deserialize)] pub struct EcdsaPublicKeyArgKeyId { pub name: String, pub curve: EcdsaCurve } - #[derive(CandidType, Deserialize)] pub struct EcdsaPublicKeyArg { pub key_id: EcdsaPublicKeyArgKeyId, pub canister_id: Option, pub derivation_path: Vec, } - #[derive(CandidType, Deserialize)] pub struct EcdsaPublicKeyRet { pub public_key: serde_bytes::ByteBuf, pub chain_code: serde_bytes::ByteBuf, } - #[derive(CandidType, Deserialize)] pub enum HttpRequestArgMethod { #[serde(rename="get")] @@ -141,23 +119,19 @@ pub enum HttpRequestArgMethod { #[serde(rename="post")] Post, } - #[derive(CandidType, Deserialize)] pub struct HttpHeader { pub value: String, pub name: String } - #[derive(CandidType, Deserialize)] pub struct HttpResponse { pub status: candid::Nat, pub body: serde_bytes::ByteBuf, pub headers: Vec, } - #[derive(CandidType, Deserialize)] pub struct HttpRequestArgTransformInnerFunctionArg { pub context: serde_bytes::ByteBuf, pub response: HttpResponse, } - candid::define_function!(pub HttpRequestArgTransformInnerFunction : ( HttpRequestArgTransformInnerFunctionArg, ) -> (HttpResponse) query); @@ -166,7 +140,6 @@ pub struct HttpRequestArgTransformInner { pub function: HttpRequestArgTransformInnerFunction, pub context: serde_bytes::ByteBuf, } - #[derive(CandidType, Deserialize)] pub struct HttpRequestArg { pub url: String, @@ -176,7 +149,6 @@ pub struct HttpRequestArg { pub transform: Option, pub headers: Vec, } - pub type WasmModule = serde_bytes::ByteBuf; #[derive(CandidType, Deserialize)] pub enum InstallCodeArgMode { @@ -187,7 +159,6 @@ pub enum InstallCodeArgMode { #[serde(rename="install")] Install, } - #[derive(CandidType, Deserialize)] pub struct InstallCodeArg { pub arg: serde_bytes::ByteBuf, @@ -195,47 +166,37 @@ pub struct InstallCodeArg { pub mode: InstallCodeArgMode, pub canister_id: CanisterId, } - #[derive(CandidType, Deserialize)] pub struct ProvisionalCreateCanisterWithCyclesArg { pub settings: Option, pub specified_id: Option, pub amount: Option, } - #[derive(CandidType, Deserialize)] pub struct ProvisionalCreateCanisterWithCyclesRet { pub canister_id: CanisterId, } - #[derive(CandidType, Deserialize)] pub struct ProvisionalTopUpCanisterArg { pub canister_id: CanisterId, pub amount: candid::Nat, } - #[derive(CandidType, Deserialize)] pub struct SignWithEcdsaArgKeyId { pub name: String, pub curve: EcdsaCurve } - #[derive(CandidType, Deserialize)] pub struct SignWithEcdsaArg { pub key_id: SignWithEcdsaArgKeyId, pub derivation_path: Vec, pub message_hash: serde_bytes::ByteBuf, } - #[derive(CandidType, Deserialize)] pub struct SignWithEcdsaRet { pub signature: serde_bytes::ByteBuf } - #[derive(CandidType, Deserialize)] pub struct StartCanisterArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct StopCanisterArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct UninstallCodeArg { pub canister_id: CanisterId } - #[derive(CandidType, Deserialize)] pub struct UpdateSettingsArg { pub canister_id: Principal, @@ -244,46 +205,32 @@ pub struct UpdateSettingsArg { pub struct Service<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> Service<'a> { - pub async fn bitcoin_get_balance(&self, arg0: GetBalanceRequest) -> Result< - Satoshi - > { + pub async fn bitcoin_get_balance(&self, arg0: GetBalanceRequest) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_balance").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, Satoshi)?) } - pub async fn bitcoin_get_current_fee_percentiles( - &self, - arg0: GetCurrentFeePercentilesRequest, - ) -> Result> { + pub async fn bitcoin_get_current_fee_percentiles(&self, arg0: GetCurrentFeePercentilesRequest) -> Result> { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_current_fee_percentiles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, Vec)?) } - pub async fn bitcoin_get_utxos(&self, arg0: GetUtxosRequest) -> Result< - GetUtxosResponse - > { + pub async fn bitcoin_get_utxos(&self, arg0: GetUtxosRequest) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_utxos").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, GetUtxosResponse)?) } - pub async fn bitcoin_send_transaction( - &self, - arg0: SendTransactionRequest, - ) -> Result<()> { + pub async fn bitcoin_send_transaction(&self, arg0: SendTransactionRequest) -> Result<()> { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_send_transaction").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } - pub async fn canister_status(&self, arg0: CanisterStatusArg) -> Result< - CanisterStatusRet - > { + pub async fn canister_status(&self, arg0: CanisterStatusArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "canister_status").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, CanisterStatusRet)?) } - pub async fn create_canister(&self, arg0: CreateCanisterArg) -> Result< - CreateCanisterRet - > { + pub async fn create_canister(&self, arg0: CreateCanisterArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "create_canister").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, CreateCanisterRet)?) @@ -298,16 +245,12 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "deposit_cycles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } - pub async fn ecdsa_public_key(&self, arg0: EcdsaPublicKeyArg) -> Result< - EcdsaPublicKeyRet - > { + pub async fn ecdsa_public_key(&self, arg0: EcdsaPublicKeyArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "ecdsa_public_key").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, EcdsaPublicKeyRet)?) } - pub async fn http_request(&self, arg0: HttpRequestArg) -> Result< - HttpResponse - > { + pub async fn http_request(&self, arg0: HttpRequestArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "http_request").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, HttpResponse)?) @@ -317,18 +260,12 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "install_code").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } - pub async fn provisional_create_canister_with_cycles( - &self, - arg0: ProvisionalCreateCanisterWithCyclesArg, - ) -> Result { + pub async fn provisional_create_canister_with_cycles(&self, arg0: ProvisionalCreateCanisterWithCyclesArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "provisional_create_canister_with_cycles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, ProvisionalCreateCanisterWithCyclesRet)?) } - pub async fn provisional_top_up_canister( - &self, - arg0: ProvisionalTopUpCanisterArg, - ) -> Result<()> { + pub async fn provisional_top_up_canister(&self, arg0: ProvisionalTopUpCanisterArg) -> Result<()> { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "provisional_top_up_canister").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) @@ -338,9 +275,7 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "raw_rand").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, serde_bytes::ByteBuf)?) } - pub async fn sign_with_ecdsa(&self, arg0: SignWithEcdsaArg) -> Result< - SignWithEcdsaRet - > { + pub async fn sign_with_ecdsa(&self, arg0: SignWithEcdsaArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "sign_with_ecdsa").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes, SignWithEcdsaRet)?) diff --git a/rust/candid_parser/tests/assets/ok/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs index 25095093..939a8378 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.rs +++ b/rust/candid_parser/tests/assets/ok/recursion.rs @@ -7,14 +7,11 @@ use ic_cdk::api::call::CallResult as Result; candid::define_function!(pub T : (S) -> ()); #[derive(CandidType, Deserialize)] pub struct Node { pub head: candid::Nat, pub tail: Box } - #[derive(CandidType, Deserialize)] pub struct List(Option); - pub type A = Box; #[derive(CandidType, Deserialize)] pub struct B(Option); - #[derive(CandidType, Deserialize)] pub enum Tree { #[serde(rename="branch")] @@ -22,18 +19,16 @@ pub enum Tree { #[serde(rename="leaf")] Leaf(candid::Int), } - candid::define_function!(pub StreamInnerNext : () -> (Stream) query); #[derive(CandidType, Deserialize)] pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } - #[derive(CandidType, Deserialize)] pub struct Stream(Option); - candid::define_service!(pub S : { "f" : T::ty(); "g" : candid::func!((List) -> (B, Tree, Stream)); }); + pub struct Service(pub Principal); impl Service { pub async fn f(&self, arg0: S) -> Result<()> { @@ -45,3 +40,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.rs b/rust/candid_parser/tests/assets/ok/recursive_class.rs index 3edd2249..eda0084e 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.rs +++ b/rust/candid_parser/tests/assets/ok/recursive_class.rs @@ -5,6 +5,7 @@ use candid::{self, CandidType, Deserialize, Principal, Encode, Decode}; use ic_cdk::api::call::CallResult as Result; candid::define_service!(pub S : { "next" : candid::func!(() -> (S)) }); + pub struct Service(pub Principal); impl Service { pub async fn next(&self) -> Result<(S,)> { @@ -13,3 +14,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index d4ac89b4..bdf308f9 100644 --- a/rust/candid_parser/tests/assets/ok/service.rs +++ b/rust/candid_parser/tests/assets/ok/service.rs @@ -32,3 +32,4 @@ impl Service { } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + diff --git a/rust/candid_parser/tests/assets/ok/unicode.rs b/rust/candid_parser/tests/assets/ok/unicode.rs index 6bb2c33f..ca077654 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.rs +++ b/rust/candid_parser/tests/assets/ok/unicode.rs @@ -15,7 +15,6 @@ pub struct A { #[serde(rename="字 段 名2")] pub _3133479156_: candid::Nat, } - #[derive(CandidType, Deserialize)] pub enum B { #[serde(rename="")] @@ -39,9 +38,10 @@ impl Service { pub async fn _3300066460_(&self, arg0: A) -> Result<(B,)> { ic_cdk::call(self.0, "函数名", (arg0,)).await } - pub async fn _2669435454_(&self, arg0: candid::Nat) -> Result< - (candid::Nat,) - > { ic_cdk::call(self.0, "👀", (arg0,)).await } + pub async fn _2669435454_(&self, arg0: candid::Nat) -> Result<(candid::Nat,)> { + ic_cdk::call(self.0, "👀", (arg0,)).await + } } pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa pub const service : Service = Service(CANISTER_ID); + From d7955b8e3bf21db89fe9e9f29396ec263d41682c Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sat, 27 Apr 2024 21:37:38 -0700 Subject: [PATCH 19/25] move pp_actor inside impl --- rust/candid_parser/src/bindings/rust.rs | 97 ++++++++++++------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 4810b527..561e31b3 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -177,7 +177,7 @@ impl<'a> State<'a> { // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), - Record(ref fs) => self.pp_record_fields(fs, false), + Record(ref fs) => self.pp_record_fields(fs, false, is_ref), Variant(_) => unreachable!(), // not possible after rewriting Func(_) => unreachable!(), // not possible after rewriting Service(_) => unreachable!(), // not possible after rewriting @@ -225,7 +225,7 @@ impl<'a> State<'a> { } } } - fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { + fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { let tuple = fs.iter().enumerate().map(|(i, f)| { let lab = i.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); @@ -234,31 +234,31 @@ impl<'a> State<'a> { } else { RcDoc::nil() }; - let res = vis.append(self.pp_ty(&f.ty, false)).append(","); + let res = vis.append(self.pp_ty(&f.ty, is_ref)).append(","); self.state.pop_state(old, StateElem::Label(&lab)); res }); enclose("(", RcDoc::concat(tuple), ")") } - fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool) -> RcDoc<'b> { + fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool, is_ref: bool) -> RcDoc<'b> { let lab = field.id.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); let res = self .pp_label(&field.id, false, need_vis) .append(kwd(":")) - .append(self.pp_ty(&field.ty, false)); + .append(self.pp_ty(&field.ty, is_ref)); self.state.pop_state(old, StateElem::Label(&lab)); res } - fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool) -> RcDoc<'b> { + fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::TypeStr("record")); let res = if is_tuple(fs) { // TODO check if there is no name/attr in the label subtree - self.pp_tuple(fs, need_vis) + self.pp_tuple(fs, need_vis, is_ref) } else { let fields: Vec<_> = fs .iter() - .map(|f| self.pp_record_field(f, need_vis)) + .map(|f| self.pp_record_field(f, need_vis, is_ref)) .collect(); let fields = concat(fields.into_iter(), ","); enclose_space("{", fields, "}") @@ -273,7 +273,7 @@ impl<'a> State<'a> { TypeInner::Null => self.pp_label(&field.id, true, false), TypeInner::Record(fs) => self .pp_label(&field.id, true, false) - .append(self.pp_record_fields(fs, false)), + .append(self.pp_record_fields(fs, false, false)), _ => self.pp_label(&field.id, true, false).append(enclose( "(", self.pp_ty(&field.ty, false), @@ -328,7 +328,7 @@ impl<'a> State<'a> { .append("struct ") .append(name) .append(" ") - .append(self.pp_record_fields(fs, true)) + .append(self.pp_record_fields(fs, true, false)) .append(separator) } TypeInner::Variant(fs) => derive @@ -424,48 +424,43 @@ impl<'a> State<'a> { self.state.pop_state(old, lab); res } -} -fn pp_function<'a>(config: &'a Config, id: &'a str, func: &'a Function) -> Method { - let env = TypeEnv::default(); - let mut state = State { - state: crate::configs::State::new(&config.tree, &env), - recs: RecPoints::default(), - }; - let args: Vec<_> = func - .args - .iter() - .enumerate() - .map(|(i, ty)| (RcDoc::<()>::text(format!("arg{i}")), state.pp_ty(ty, true))) - .collect(); - let rets: Vec<_> = func.rets.iter().map(|ty| state.pp_ty(ty, true)).collect(); - let mode = if func.is_query() { "query" } else { "update" }.to_string(); - Method { - name: id.to_string(), - args: args - .into_iter() - .map(|(id, t)| { - ( - id.pretty(LINE_WIDTH).to_string(), - t.pretty(LINE_WIDTH).to_string(), - ) - }) - .collect(), - rets: rets - .into_iter() - .map(|x| x.pretty(LINE_WIDTH).to_string()) - .collect(), - mode, + fn pp_function(&mut self, id: &str, func: &Function) -> Method { + let args: Vec<_> = func + .args + .iter() + .enumerate() + .map(|(i, ty)| (RcDoc::<()>::text(format!("arg{i}")), self.pp_ty(ty, true))) + .collect(); + let rets: Vec<_> = func.rets.iter().map(|ty| self.pp_ty(ty, true)).collect(); + let mode = if func.is_query() { "query" } else { "update" }.to_string(); + Method { + name: id.to_string(), + args: args + .into_iter() + .map(|(id, t)| { + ( + id.pretty(LINE_WIDTH).to_string(), + t.pretty(LINE_WIDTH).to_string(), + ) + }) + .collect(), + rets: rets + .into_iter() + .map(|x| x.pretty(LINE_WIDTH).to_string()) + .collect(), + mode, + } } -} -fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> Vec { - // TODO trace to service before we figure out what canister means in Rust - let serv = env.as_service(actor).unwrap(); - let mut res = Vec::new(); - for (id, func) in serv.iter() { - let func = env.as_func(func).unwrap(); - res.push(pp_function(config, id, func)); + fn pp_actor(&mut self, actor: &Type) -> Vec { + // TODO trace to service before we figure out what canister means in Rust + let serv = self.state.env.as_service(actor).unwrap(); + let mut res = Vec::new(); + for (id, func) in serv.iter() { + let func = self.state.env.as_func(func).unwrap(); + res.push(self.pp_function(id, func)); + } + res } - res } #[derive(Serialize)] pub struct Output { @@ -503,7 +498,7 @@ pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { }; let defs = state.pp_defs(&def_list); let methods = if let Some(actor) = &actor { - pp_actor(config, &env, actor) + state.pp_actor(actor) } else { Vec::new() }; From 99cdf4c2d19bb3a4cb39b29dbd9dd84b95045d63 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:14:53 -0700 Subject: [PATCH 20/25] fix --- rust/candid_parser/src/bindings/rust.rs | 28 +++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 561e31b3..55857f94 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -425,15 +425,33 @@ impl<'a> State<'a> { res } fn pp_function(&mut self, id: &str, func: &Function) -> Method { + let old = self.state.push_state(&StateElem::Label(id)); let args: Vec<_> = func .args .iter() .enumerate() - .map(|(i, ty)| (RcDoc::<()>::text(format!("arg{i}")), self.pp_ty(ty, true))) + .map(|(i, ty)| { + let lab = format!("arg{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = (RcDoc::<()>::text(lab.clone()), self.pp_ty(ty, true)); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }) + .collect(); + let rets: Vec<_> = func + .rets + .iter() + .enumerate() + .map(|(i, ty)| { + let lab = format!("ret{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let res = self.pp_ty(ty, true); + self.state.pop_state(old, StateElem::Label(&lab)); + res + }) .collect(); - let rets: Vec<_> = func.rets.iter().map(|ty| self.pp_ty(ty, true)).collect(); let mode = if func.is_query() { "query" } else { "update" }.to_string(); - Method { + let res = Method { name: id.to_string(), args: args .into_iter() @@ -449,7 +467,9 @@ impl<'a> State<'a> { .map(|x| x.pretty(LINE_WIDTH).to_string()) .collect(), mode, - } + }; + self.state.pop_state(old, StateElem::Label(id)); + res } fn pp_actor(&mut self, actor: &Type) -> Vec { // TODO trace to service before we figure out what canister means in Rust From 5dc16077aff598a285e6a201a76344b288c798ae Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:48:34 -0700 Subject: [PATCH 21/25] refactor --- rust/candid_parser/src/bindings/rust.rs | 138 +++++++++--------- .../candid_parser/src/bindings/rust_agent.hbs | 4 +- rust/candid_parser/src/bindings/rust_call.hbs | 6 +- rust/candid_parser/tests/parse_type.rs | 12 +- tools/didc/src/main.rs | 13 +- 5 files changed, 94 insertions(+), 79 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 55857f94..ba943194 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -8,53 +8,8 @@ use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInne use convert_case::{Case, Casing}; use pretty::RcDoc; use serde::Serialize; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; -#[derive(Clone)] -pub enum Target { - CanisterCall, - Agent, - CanisterStub, -} - -//#[derive(Clone)] -pub struct Config { - candid_crate: String, - tree: ConfigTree, - canister_id: Option, - service_name: String, - target: Target, -} -impl Config { - pub fn new(configs: Configs) -> Self { - let tree = ConfigTree::from_configs("rust", configs).unwrap(); - Config { - candid_crate: "candid".to_string(), - tree, - canister_id: None, - service_name: "service".to_string(), - target: Target::CanisterCall, - } - } - pub fn set_candid_crate(&mut self, name: String) -> &mut Self { - self.candid_crate = name; - self - } - /// Only generates SERVICE struct if canister_id is not provided - pub fn set_canister_id(&mut self, id: candid::Principal) -> &mut Self { - self.canister_id = Some(id); - self - } - /// Service name when canister id is provided - pub fn set_service_name(&mut self, name: String) -> &mut Self { - self.service_name = name; - self - } - pub fn set_target(&mut self, name: Target) -> &mut Self { - self.target = name; - self - } -} #[derive(Default, Deserialize, Clone, Debug)] pub struct BindingConfig { name: Option, @@ -482,29 +437,19 @@ impl<'a> State<'a> { res } } -#[derive(Serialize)] +#[derive(Serialize, Debug)] pub struct Output { - candid_crate: String, - service_name: String, - canister_id: Option, type_defs: String, methods: Vec, } -#[derive(Serialize)] +#[derive(Serialize, Debug)] pub struct Method { name: String, args: Vec<(String, String)>, rets: Vec, mode: String, } - -pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { - let source = match &config.target { - Target::CanisterCall => include_str!("rust_call.hbs"), - Target::Agent => include_str!("rust_agent.hbs"), - Target::CanisterStub => unimplemented!(), - }; - let hbs = get_hbs(); +pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> Output { let (env, actor) = nominalize_all(env, actor); let def_list: Vec<_> = if let Some(actor) = &actor { chase_actor(&env, actor).unwrap() @@ -513,7 +458,7 @@ pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { }; let recs = infer_rec(&env, &def_list).unwrap(); let mut state = State { - state: crate::configs::State::new(&config.tree, &env), + state: crate::configs::State::new(&tree.0, &env), recs, }; let defs = state.pp_defs(&def_list); @@ -522,15 +467,63 @@ pub fn compile(config: &Config, env: &TypeEnv, actor: &Option) -> String { } else { Vec::new() }; - let data = Output { - candid_crate: config.candid_crate.clone(), - service_name: config.service_name.to_case(Case::Pascal), - canister_id: config.canister_id, + Output { type_defs: defs.pretty(LINE_WIDTH).to_string(), methods, + } +} +pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) -> String { + let hbs = get_hbs(); + #[derive(Serialize)] + struct HBOutput { + #[serde(flatten)] + external: BTreeMap, + type_defs: String, + methods: Vec, + } + let data = HBOutput { + type_defs: output.type_defs, + methods: output.methods, + external: config.0, }; - hbs.render_template(source, &data).unwrap() + hbs.render_template(template, &data).unwrap() } +pub struct Config(ConfigTree); +impl Config { + pub fn new(configs: Configs) -> Self { + Self(ConfigTree::from_configs("rust", configs).unwrap()) + } +} +pub struct ExternalConfig(pub BTreeMap); +impl Default for ExternalConfig { + fn default() -> Self { + Self( + [ + ("candid_crate", "candid"), + ("service_name", "service"), + ("target", "canister_call"), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + ) + } +} +pub fn compile( + tree: &Config, + env: &TypeEnv, + actor: &Option, + external: ExternalConfig, +) -> String { + let source = match external.0.get("target").map(|s| s.as_str()) { + Some("canister_call") | None => include_str!("rust_call.hbs"), + Some("agent") => include_str!("rust_agent.hbs"), + _ => unimplemented!(), + }; + let output = emit_bindgen(tree, env, actor); + output_handlebar(output, external, source) +} + pub enum TypePath { Id(String), Opt, @@ -755,6 +748,21 @@ fn get_hbs() -> handlebars::Handlebars<'static> { }, ), ); + hbs.register_helper( + "PascalCase", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let s = h.param(0).unwrap().value().as_str().unwrap(); + out.write(s.to_case(Case::Pascal).as_ref())?; + Ok(()) + }, + ), + ); hbs.register_helper( "id", Box::new( diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs index 88007d73..9a1f0f95 100644 --- a/rust/candid_parser/src/bindings/rust_agent.hbs +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -6,8 +6,8 @@ type Result = std::result::Result; {{type_defs}} {{#if methods}} -pub struct {{service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); -impl<'a> {{service_name}}<'a> { +pub struct {{PascalCase service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); +impl<'a> {{PascalCase service_name}}<'a> { {{#each methods}} pub async fn {{id this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { let args = Encode!({{#each this.args}}&{{this.0}}{{#unless @last}},{{/unless}}{{/each}})?; diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs index 6cb7621c..eb000790 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -6,8 +6,8 @@ use ic_cdk::api::call::CallResult as Result; {{type_defs}} {{#if methods}} -pub struct {{service_name}}(pub Principal); -impl {{service_name}} { +pub struct {{PascalCase service_name}}(pub Principal); +impl {{PascalCase service_name}} { {{#each methods}} pub async fn {{id this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { ic_cdk::call(self.0, "{{escape_debug this.name}}", ({{#each this.args}}{{this.0}},{{/each}})).await @@ -16,6 +16,6 @@ impl {{service_name}} { } {{#if canister_id}} pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} -pub const {{snake_case service_name}} : {{service_name}} = {{service_name}}(CANISTER_ID); +pub const {{snake_case service_name}} : {{PascalCase service_name}} = {{PascalCase service_name}}(CANISTER_ID); {{/if}} {{/if}} diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index caba565d..5f645c1f 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -63,14 +63,18 @@ fn compiler_test(resource: &str) { } } { + use rust::{Config, ExternalConfig}; use std::str::FromStr; - let mut config = rust::Config::new(Configs::from_str("").unwrap()); - config.set_canister_id(candid::Principal::from_text("aaaaa-aa").unwrap()); + let config = Config::new(Configs::from_str("").unwrap()); + let mut external = ExternalConfig::default(); + external + .0 + .insert("canister_id".to_string(), "aaaaa-aa".to_string()); if filename.file_name().unwrap().to_str().unwrap() == "management.did" { - config.set_target(rust::Target::Agent); + external.0.insert("target".to_string(), "agent".to_string()); } let mut output = mint.new_goldenfile(filename.with_extension("rs")).unwrap(); - let content = rust::compile(&config, &env, &actor); + let content = rust::compile(&config, &env, &actor, external); writeln!(output, "{content}").unwrap(); } { diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index c5ee0b1a..daeef0ca 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -216,13 +216,16 @@ fn main() -> Result<()> { "did" => candid_parser::pretty::candid::compile(&env, &actor), "mo" => candid_parser::bindings::motoko::compile(&env, &actor), "rs" => { - let config = candid_parser::bindings::rust::Config::new(configs); - candid_parser::bindings::rust::compile(&config, &env, &actor) + use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; + let config = Config::new(configs); + compile(&config, &env, &actor, ExternalConfig::default()) } "rs-agent" => { - let mut config = candid_parser::bindings::rust::Config::new(configs); - config.set_target(candid_parser::bindings::rust::Target::Agent); - candid_parser::bindings::rust::compile(&config, &env, &actor) + use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; + let config = Config::new(configs); + let mut external = ExternalConfig::default(); + external.0.insert("target".to_string(), "agent".to_string()); + compile(&config, &env, &actor, external) } _ => unreachable!(), }; From d9a7519404f1e9972367582877fe232f6a2d7311 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:44:18 -0700 Subject: [PATCH 22/25] init args --- rust/candid_parser/src/bindings/rust.rs | 91 ++++++++++++------- .../candid_parser/src/bindings/rust_agent.hbs | 4 +- rust/candid_parser/src/bindings/rust_call.hbs | 4 +- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index ba943194..bbb941c6 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -20,8 +20,15 @@ pub struct BindingConfig { impl ConfigState for BindingConfig { fn merge_config(&mut self, config: &Self, elem: Option<&StateElem>, _is_recursive: bool) { self.name = config.name.clone(); - self.use_type = config.use_type.clone(); - // matched attributes can survive across labels in order to apply attributes to all labels at this level + // match use_type can survive across types, so that label.use_type works + if !matches!(elem, Some(StateElem::Label(_))) { + if let Some(use_type) = &config.use_type { + self.use_type = Some(use_type.clone()); + } + } else { + self.use_type = config.use_type.clone(); + } + // matched attributes can survive across labels, so that record.attributes works if matches!(elem, Some(StateElem::Label(_))) { if let Some(attr) = &config.attributes { self.attributes = Some(attr.clone()); @@ -381,6 +388,12 @@ impl<'a> State<'a> { } fn pp_function(&mut self, id: &str, func: &Function) -> Method { let old = self.state.push_state(&StateElem::Label(id)); + let name = self + .state + .config + .name + .clone() + .unwrap_or_else(|| ident(id, Some(Case::Snake)).pretty(LINE_WIDTH).to_string()); let args: Vec<_> = func .args .iter() @@ -388,9 +401,15 @@ impl<'a> State<'a> { .map(|(i, ty)| { let lab = format!("arg{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = (RcDoc::<()>::text(lab.clone()), self.pp_ty(ty, true)); + let name = self + .state + .config + .name + .clone() + .unwrap_or_else(|| lab.clone()); + let res = self.pp_ty(ty, true); self.state.pop_state(old, StateElem::Label(&lab)); - res + (name, res) }) .collect(); let rets: Vec<_> = func @@ -407,15 +426,11 @@ impl<'a> State<'a> { .collect(); let mode = if func.is_query() { "query" } else { "update" }.to_string(); let res = Method { - name: id.to_string(), + name, + original_name: id.to_string(), args: args .into_iter() - .map(|(id, t)| { - ( - id.pretty(LINE_WIDTH).to_string(), - t.pretty(LINE_WIDTH).to_string(), - ) - }) + .map(|(id, t)| (id, t.pretty(LINE_WIDTH).to_string())) .collect(), rets: rets .into_iter() @@ -426,25 +441,51 @@ impl<'a> State<'a> { self.state.pop_state(old, StateElem::Label(id)); res } - fn pp_actor(&mut self, actor: &Type) -> Vec { - // TODO trace to service before we figure out what canister means in Rust - let serv = self.state.env.as_service(actor).unwrap(); + fn pp_actor(&mut self, actor: &Type) -> (Vec, Option>) { + let actor = self.state.env.trace_type(actor).unwrap(); + let init = if let TypeInner::Class(args, _) = actor.as_ref() { + let old = self.state.push_state(&StateElem::Label("init")); + let args: Vec<_> = args + .iter() + .enumerate() + .map(|(i, ty)| { + let lab = format!("arg{i}"); + let old = self.state.push_state(&StateElem::Label(&lab)); + let name = self + .state + .config + .name + .clone() + .unwrap_or_else(|| lab.clone()); + let res = self.pp_ty(ty, true); + self.state.pop_state(old, StateElem::Label(&lab)); + (name, res.pretty(LINE_WIDTH).to_string()) + }) + .collect(); + self.state.pop_state(old, StateElem::Label("init")); + Some(args) + } else { + None + }; + let serv = self.state.env.as_service(&actor).unwrap(); let mut res = Vec::new(); for (id, func) in serv.iter() { let func = self.state.env.as_func(func).unwrap(); res.push(self.pp_function(id, func)); } - res + (res, init) } } #[derive(Serialize, Debug)] pub struct Output { type_defs: String, methods: Vec, + init_args: Option>, } #[derive(Serialize, Debug)] pub struct Method { name: String, + original_name: String, args: Vec<(String, String)>, rets: Vec, mode: String, @@ -462,14 +503,15 @@ pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> Outpu recs, }; let defs = state.pp_defs(&def_list); - let methods = if let Some(actor) = &actor { + let (methods, init_args) = if let Some(actor) = &actor { state.pp_actor(actor) } else { - Vec::new() + (Vec::new(), None) }; Output { type_defs: defs.pretty(LINE_WIDTH).to_string(), methods, + init_args, } } pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) -> String { @@ -763,21 +805,6 @@ fn get_hbs() -> handlebars::Handlebars<'static> { }, ), ); - hbs.register_helper( - "id", - Box::new( - |h: &Helper, - _: &Handlebars, - _: &Context, - _: &mut RenderContext, - out: &mut dyn Output| - -> HelperResult { - let s = h.param(0).unwrap().value().as_str().unwrap(); - out.write(&ident(s, Some(Case::Snake)).pretty(LINE_WIDTH).to_string())?; - Ok(()) - }, - ), - ); hbs.register_helper( "vec_to_arity", Box::new( diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs index 9a1f0f95..f48fefc6 100644 --- a/rust/candid_parser/src/bindings/rust_agent.hbs +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -9,9 +9,9 @@ type Result = std::result::Result; pub struct {{PascalCase service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> {{PascalCase service_name}}<'a> { {{#each methods}} - pub async fn {{id this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { + pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { let args = Encode!({{#each this.args}}&{{this.0}}{{#unless @last}},{{/unless}}{{/each}})?; - let bytes = self.1.{{this.mode}}(&self.0, "{{escape_debug this.name}}").with_arg(args).{{#if (eq this.mode "query")}}call{{else}}call_and_wait(){{/if}}.await?; + let bytes = self.1.{{this.mode}}(&self.0, "{{escape_debug this.original_name}}").with_arg(args).{{#if (eq this.mode "query")}}call{{else}}call_and_wait(){{/if}}.await?; Ok(Decode!(&bytes{{#each this.rets}}, {{this}}{{/each}})?) } {{/each}} diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs index eb000790..6be0fe4b 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -9,8 +9,8 @@ use ic_cdk::api::call::CallResult as Result; pub struct {{PascalCase service_name}}(pub Principal); impl {{PascalCase service_name}} { {{#each methods}} - pub async fn {{id this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { - ic_cdk::call(self.0, "{{escape_debug this.name}}", ({{#each this.args}}{{this.0}},{{/each}})).await + pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: {{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { + ic_cdk::call(self.0, "{{escape_debug this.original_name}}", ({{#each this.args}}{{this.0}},{{/each}})).await } {{/each}} } From 1d623c544457b43f3f98a6363f90015277fdcb69 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Wed, 1 May 2024 16:32:03 -0700 Subject: [PATCH 23/25] add test --- rust/candid_parser/tests/assets/example.toml | 11 ++ rust/candid_parser/tests/assets/ok/example.rs | 126 ++++++++++-------- rust/candid_parser/tests/parse_type.rs | 16 ++- 3 files changed, 97 insertions(+), 56 deletions(-) create mode 100644 rust/candid_parser/tests/assets/example.toml diff --git a/rust/candid_parser/tests/assets/example.toml b/rust/candid_parser/tests/assets/example.toml new file mode 100644 index 00000000..90d9f14a --- /dev/null +++ b/rust/candid_parser/tests/assets/example.toml @@ -0,0 +1,11 @@ +[rust] +attributes = "#[derive(CandidType, Deserialize, Debug)]" +visibility = "pub(crate)" +List.name = "MyList" +Nested41.variant.A = { name = "AAA", attributes = "#[serde(skip_deserializing)]" } +ListInner.attributes = "#derive[CandidType, Deserialize, Clone]" +ListInner.record = { visibility = "", head.name = "HEAD", attributes = "#[serde(skip_deserializing)]", tail.use_type = "Arc" } +my_type = { visibility = "", name = "CanisterId" } +nat.use_type = "u128" +BrokerFindRet = { name = "BrokerReturn", visibility = "pub" } +g1 = { name = "G11", arg0.name = "id", arg1.name = "list", arg2.name = "is_okay", ret0.use_type = "i128" } diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index 6dec7032..18891eb8 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -4,68 +4,88 @@ use candid::{self, CandidType, Deserialize, Principal, Encode, Decode}; use ic_cdk::api::call::CallResult as Result; -#[derive(CandidType, Deserialize)] -pub struct B (pub candid::Int,pub candid::Nat,); -#[derive(CandidType, Deserialize)] -pub struct Node { pub head: candid::Nat, pub tail: Box } -#[derive(CandidType, Deserialize)] -pub struct List(Option); -pub type A = Box; -#[derive(CandidType, Deserialize)] -pub struct B(Option); -#[derive(CandidType, Deserialize)] -pub enum Tree { +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct B (pub(crate) candid::Int,pub(crate) u128,); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Node { pub(crate) head: u128, pub(crate) tail: Box } +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct List(Option); +pub(crate) type A = Box; +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct B(Option); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum Tree { #[serde(rename="branch")] Branch{ val: candid::Int, left: Box, right: Box }, #[serde(rename="leaf")] Leaf(candid::Int), } -candid::define_function!(pub StreamInnerNext : () -> (Stream) query); -#[derive(CandidType, Deserialize)] -pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext } -#[derive(CandidType, Deserialize)] -pub struct Stream(Option); -candid::define_service!(pub S : { +candid::define_function!(pub(crate) StreamInnerNext : () -> (Stream) query); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct StreamInner { + pub(crate) head: u128, + pub(crate) next: StreamInnerNext, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Stream(Option); +candid::define_service!(pub(crate) S : { "f" : T::ty(); "g" : candid::func!((List) -> (B, Tree, Stream)); }); -candid::define_function!(pub T : (S) -> ()); -pub type MyType = Principal; -#[derive(CandidType, Deserialize)] -pub struct ListInner { pub head: candid::Int, pub tail: Box } -#[derive(CandidType, Deserialize)] -pub struct List(Option); -#[derive(CandidType, Deserialize)] -pub struct Nested3 { pub _0_: candid::Nat, pub _42_: candid::Nat, pub _43_: u8 } -#[derive(CandidType, Deserialize)] -pub enum Nested41 { _42_, A, B, C } -#[derive(CandidType, Deserialize)] -pub struct Nested { - pub _0_: candid::Nat, - pub _1_: candid::Nat, - pub _2_: (candid::Nat,candid::Int,), - pub _3_: Nested3, - pub _40_: candid::Nat, - pub _41_: Nested41, - pub _42_: candid::Nat, +candid::define_function!(pub(crate) T : (S) -> ()); +type CanisterId = Principal; +#derive[CandidType, Deserialize, Clone] +pub(crate) struct ListInner { + #[serde(skip_deserializing)] + #[serde(rename="head")] + HEAD: candid::Int, + #[serde(skip_deserializing)] + tail: Arc, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct MyList(Option); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Nested3 { + pub(crate) _0_: u128, + pub(crate) _42_: u128, + pub(crate) _43_: u8, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum Nested41 { + _42_, + #[serde(skip_deserializing)] + #[serde(rename="A")] + AAA, + B, + C, +} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct Nested { + pub(crate) _0_: u128, + pub(crate) _1_: u128, + pub(crate) _2_: (u128,candid::Int,), + pub(crate) _3_: Nested3, + pub(crate) _40_: u128, + pub(crate) _41_: Nested41, + pub(crate) _42_: u128, } -candid::define_service!(pub BrokerFindRet : { +candid::define_service!(pub BrokerReturn : { "current" : candid::func!(() -> (u32)); "up" : candid::func!(() -> ()); }); -candid::define_service!(pub Broker : { - "find" : candid::func!((String) -> (BrokerFindRet)); +candid::define_service!(pub(crate) Broker : { + "find" : candid::func!((String) -> (BrokerReturn)); }); -#[derive(CandidType, Deserialize)] -pub enum HArg1 { A(candid::Nat), B(Option) } -#[derive(CandidType, Deserialize)] -pub struct HRet42 {} -#[derive(CandidType, Deserialize)] -pub struct HRet { pub _42_: HRet42, pub id: candid::Nat } -candid::define_function!(pub FArg1 : (i32) -> (i64)); -candid::define_function!(pub F : (List, FArg1) -> (Option)); -#[derive(CandidType, Deserialize)] -pub enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(B) } +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum HArg1 { A(u128), B(Option) } +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct HRet42 {} +#[derive(CandidType, Deserialize, Debug)] +pub(crate) struct HRet { pub(crate) _42_: HRet42, pub(crate) id: u128 } +candid::define_function!(pub(crate) FArg1 : (i32) -> (i64)); +candid::define_function!(pub(crate) F : (MyList, FArg1) -> (Option)); +#[derive(CandidType, Deserialize, Debug)] +pub(crate) enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(B) } pub struct Service(pub Principal); impl Service { @@ -81,13 +101,13 @@ impl Service { pub async fn g(&self, arg0: List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await } - pub async fn g_1(&self, arg0: MyType, arg1: List, arg2: Option, arg3: Nested) -> Result<(candid::Int,Broker,)> { - ic_cdk::call(self.0, "g1", (arg0,arg1,arg2,arg3,)).await + pub async fn G11(&self, id: CanisterId, list: MyList, is_okay: Option, arg3: Nested) -> Result<(i128,Broker,)> { + ic_cdk::call(self.0, "g1", (id,list,is_okay,arg3,)).await } - pub async fn h(&self, arg0: Vec>, arg1: HArg1, arg2: Option) -> Result<(HRet,)> { + pub async fn h(&self, arg0: Vec>, arg1: HArg1, arg2: Option) -> Result<(HRet,)> { ic_cdk::call(self.0, "h", (arg0,arg1,arg2,)).await } - pub async fn i(&self, arg0: List, arg1: FArg1) -> Result<(Option,)> { + pub async fn i(&self, arg0: MyList, arg1: FArg1) -> Result<(Option,)> { ic_cdk::call(self.0, "i", (arg0,arg1,)).await } pub async fn x(&self, arg0: A, arg1: B) -> Result<(Option,Option,)> { diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 5f645c1f..2d7bb219 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -65,13 +65,23 @@ fn compiler_test(resource: &str) { { use rust::{Config, ExternalConfig}; use std::str::FromStr; - let config = Config::new(Configs::from_str("").unwrap()); + let mut config = Config::new(Configs::from_str("").unwrap()); let mut external = ExternalConfig::default(); external .0 .insert("canister_id".to_string(), "aaaaa-aa".to_string()); - if filename.file_name().unwrap().to_str().unwrap() == "management.did" { - external.0.insert("target".to_string(), "agent".to_string()); + match filename.file_name().unwrap().to_str().unwrap() { + "management.did" => { + drop(external.0.insert("target".to_string(), "agent".to_string())) + } + "example.did" => { + let configs = std::fs::read_to_string(base_path.join("example.toml")) + .unwrap() + .parse::() + .unwrap(); + config = Config::new(configs); + } + _ => (), } let mut output = mint.new_goldenfile(filename.with_extension("rs")).unwrap(); let content = rust::compile(&config, &env, &actor, external); From 1266203a2aebf94d9f99d85ea7a9138fd25e7c95 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Thu, 2 May 2024 16:21:20 -0700 Subject: [PATCH 24/25] add_config --- rust/candid_parser/src/configs.rs | 93 ++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index d658f092..c711e4fd 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -126,6 +126,45 @@ impl ConfigTree { } Some(tree) } + pub fn add_config(&mut self, path: &[String], config: T) { + let n = path.len(); + let mut tree: &Self = self; + let mut i = 0; + while i < n { + if let Some(subtree) = tree.subtree.get(&path[i]) { + tree = subtree; + i += 1; + } else { + break; + } + } + let mut node = Self { + state: Some(config.clone()), + subtree: BTreeMap::default(), + max_depth: 0, + }; + for k in (i + 1..n).rev() { + node = Self { + state: None, + max_depth: node.max_depth + 1, + subtree: [(path[k].clone(), node)].into_iter().collect(), + } + } + let mut tree = self; + let mut d = n as u8; + #[allow(clippy::needless_range_loop)] + for k in 0..i { + tree.max_depth = std::cmp::max(d, tree.max_depth); + tree = tree.subtree.get_mut(&path[k]).unwrap(); + d -= 1; + } + if i == n { + tree.state = Some(config); + } else { + tree.subtree.insert(path[i].clone(), node); + tree.max_depth = std::cmp::max(d, tree.max_depth); + } + } pub fn get_config(&self, path: &[String]) -> Option<(&T, bool)> { let len = path.len(); assert!(len > 0); @@ -285,7 +324,7 @@ Vec = { width = 2, size = 10 } "method:f".list = { depth = 3, size = 30 } "#; let configs = toml.parse::().unwrap(); - let tree: ConfigTree = ConfigTree::from_configs("random", configs).unwrap(); + let mut tree: ConfigTree = ConfigTree::from_configs("random", configs).unwrap(); assert_eq!(tree.state, None); assert_eq!(tree.subtree.len(), 6); assert_eq!(tree.max_depth, 2); @@ -293,6 +332,58 @@ Vec = { width = 2, size = 10 } tree.get_config(&["list".to_string()]).unwrap().0.depth, Some(20) ); + let t = T { + text: None, + depth: Some(100), + size: None, + }; + tree.add_config(&[], t.clone()); + assert_eq!(tree.state, Some(t.clone())); + tree.add_config(&["left".to_string(), "list".to_string()], t.clone()); + assert_eq!( + tree.match_exact_path(&["left".to_string(), "list".to_string()]) + .unwrap() + .depth, + Some(100) + ); + assert_eq!( + tree.match_exact_path(&["left".to_string(), "a".to_string()]), + None + ); + tree.add_config(&["left".to_string(), "a".to_string()], t.clone()); + assert_eq!( + tree.match_exact_path(&["left".to_string(), "a".to_string()]) + .unwrap() + .depth, + Some(100) + ); + assert_eq!(tree.max_depth, 2); + tree.add_config( + &["a".to_string(), "b".to_string(), "c".to_string()], + t.clone(), + ); + assert_eq!( + tree.match_exact_path(&["a".to_string(), "b".to_string(), "c".to_string()]) + .unwrap() + .depth, + Some(100) + ); + assert_eq!(tree.max_depth, 3); + tree.add_config( + &["a".to_string(), "b".to_string(), "d".to_string()], + t.clone(), + ); + assert_eq!(tree.max_depth, 3); + tree.add_config( + &[ + "a".to_string(), + "b".to_string(), + "c".to_string(), + "d".to_string(), + ], + t.clone(), + ); + assert_eq!(tree.max_depth, 4); let env = TypeEnv::default(); let mut state = State::new(&tree, &env); state.with_scope( From 2fd3c2e3467cb0463e392ee8c3271918d139f1d2 Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Thu, 2 May 2024 22:13:34 -0700 Subject: [PATCH 25/25] clippy --- rust/candid/src/ser.rs | 8 ++++---- rust/candid/src/types/mod.rs | 8 ++++---- rust/candid_parser/src/bindings/rust.rs | 8 ++++---- rust/candid_parser/src/random.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 002c6717..15750555 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -169,9 +169,9 @@ impl<'a> types::Serializer for &'a mut ValueSerializer { self.serialize_principal(blob)?; self.serialize_text(meth) } - fn serialize_option(self, v: Option<&T>) -> Result<()> + fn serialize_option(self, v: Option<&T>) -> Result<()> where - T: super::CandidType, + T: super::CandidType + ?Sized, { match v { None => { @@ -207,9 +207,9 @@ pub struct Compound<'a> { } impl<'a> types::Compound for Compound<'a> { type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<()> + fn serialize_element(&mut self, value: &T) -> Result<()> where - T: types::CandidType, + T: types::CandidType + ?Sized, { value.idl_serialize(&mut *self.ser)?; Ok(()) diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index f844ab72..aab4554a 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -81,9 +81,9 @@ pub trait Serializer: Sized { fn serialize_text(self, v: &str) -> Result<(), Self::Error>; fn serialize_null(self, v: ()) -> Result<(), Self::Error>; fn serialize_empty(self) -> Result<(), Self::Error>; - fn serialize_option(self, v: Option<&T>) -> Result<(), Self::Error> + fn serialize_option(self, v: Option<&T>) -> Result<(), Self::Error> where - T: CandidType; + T: CandidType + ?Sized; fn serialize_struct(self) -> Result; fn serialize_vec(self, len: usize) -> Result; fn serialize_blob(self, v: &[u8]) -> Result<(), Self::Error>; @@ -94,9 +94,9 @@ pub trait Serializer: Sized { pub trait Compound { type Error; - fn serialize_element(&mut self, v: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, v: &T) -> Result<(), Self::Error> where - T: CandidType; + T: CandidType + ?Sized; // Used for simulating serde(with = "serde_bytes"). We can remove this when specialization is stable in Rust, // or generalize this function to take a closure for with. #[doc(hidden)] diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index bbb941c6..afeb98ff 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -19,14 +19,14 @@ pub struct BindingConfig { } impl ConfigState for BindingConfig { fn merge_config(&mut self, config: &Self, elem: Option<&StateElem>, _is_recursive: bool) { - self.name = config.name.clone(); + self.name.clone_from(&config.name); // match use_type can survive across types, so that label.use_type works if !matches!(elem, Some(StateElem::Label(_))) { if let Some(use_type) = &config.use_type { self.use_type = Some(use_type.clone()); } } else { - self.use_type = config.use_type.clone(); + self.use_type.clone_from(&config.use_type); } // matched attributes can survive across labels, so that record.attributes works if matches!(elem, Some(StateElem::Label(_))) { @@ -34,10 +34,10 @@ impl ConfigState for BindingConfig { self.attributes = Some(attr.clone()); } } else { - self.attributes = config.attributes.clone(); + self.attributes.clone_from(&config.attributes); } if config.visibility.is_some() { - self.visibility = config.visibility.clone(); + self.visibility.clone_from(&config.visibility); } } fn update_state(&mut self, _elem: &StateElem) {} diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 56bca513..0fdc81bf 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -35,11 +35,11 @@ impl ConfigState for GenConfig { fn merge_config(&mut self, config: &Self, _elem: Option<&StateElem>, is_recursive: bool) { self.range = config.range.or(self.range); if config.text.is_some() { - self.text = config.text.clone(); + self.text.clone_from(&config.text); } self.width = config.width.or(self.width); if config.value.is_some() { - self.value = config.value.clone(); + self.value.clone_from(&config.value); } if !is_recursive { self.depth = config.depth.or(self.depth);