From a21d1b8ff255964caa63e61858fc600db544563d Mon Sep 17 00:00:00 2001 From: Yan Chen <48968912+chenyan-dfinity@users.noreply.github.com> Date: Tue, 14 May 2024 13:50:58 -0700 Subject: [PATCH] more improvements to Configs (#548) * add clone for configs * add open_path * report unused * add stub * add rust parser to extract cdk functions; didc to take external.rust section * fix mode * refactor to report span * checkpoint, use codespan_reporting * good * fix * move rust_check to cdk * bump version --- Cargo.lock | 146 +++++++++--------- rust/candid_parser/Cargo.toml | 4 +- rust/candid_parser/src/bindings/rust.rs | 83 ++++++++-- .../candid_parser/src/bindings/rust_agent.hbs | 2 +- rust/candid_parser/src/bindings/rust_call.hbs | 2 +- rust/candid_parser/src/bindings/rust_stub.hbs | 18 +++ rust/candid_parser/src/configs.rs | 124 ++++++++++++--- rust/candid_parser/tests/assets/ok/actor.rs | 2 +- rust/candid_parser/tests/assets/ok/class.rs | 2 +- rust/candid_parser/tests/assets/ok/comment.rs | 2 +- rust/candid_parser/tests/assets/ok/cyclic.rs | 2 +- rust/candid_parser/tests/assets/ok/empty.rs | 2 +- rust/candid_parser/tests/assets/ok/escape.rs | 2 +- rust/candid_parser/tests/assets/ok/example.rs | 2 +- .../candid_parser/tests/assets/ok/fieldnat.rs | 2 +- rust/candid_parser/tests/assets/ok/keyword.rs | 2 +- .../tests/assets/ok/recursion.rs | 2 +- .../tests/assets/ok/recursive_class.rs | 2 +- rust/candid_parser/tests/assets/ok/service.rs | 2 +- rust/candid_parser/tests/assets/ok/unicode.rs | 2 +- tools/didc/src/main.rs | 17 +- 21 files changed, 289 insertions(+), 133 deletions(-) create mode 100644 rust/candid_parser/src/bindings/rust_stub.hbs diff --git a/Cargo.lock b/Cargo.lock index a076a7af..04925dba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "arbitrary" @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "beef" @@ -126,7 +126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" dependencies = [ "either", - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", "syn 1.0.109", ] @@ -208,14 +208,14 @@ name = "candid_derive" version = "0.6.6" dependencies = [ "lazy_static", - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] name = "candid_parser" -version = "0.2.0-beta.0" +version = "0.2.0-beta.1" dependencies = [ "anyhow", "arbitrary", @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -289,9 +289,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -390,9 +390,9 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deunicode" -version = "1.4.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] name = "dialoguer" @@ -457,9 +457,9 @@ checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "ena" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] @@ -478,9 +478,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", @@ -526,9 +526,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -658,7 +658,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.3", + "regex-syntax", "string_cache", "term", "tiny-keccak", @@ -727,32 +727,33 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "logos" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" +checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +checksum = "8e31badd9de5131fdf4921f6473d457e3dd85b11b7f091ceb50e4df7c3eeb12a" dependencies = [ "beef", "fnv", - "proc-macro2 1.0.81", + "lazy_static", + "proc-macro2 1.0.82", "quote 1.0.36", - "regex-syntax 0.6.29", - "syn 2.0.60", + "regex-syntax", + "syn 2.0.63", ] [[package]] name = "logos-derive" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +checksum = "1c2a69b3eb68d5bd595107c9ee58d7e07fe2bb5e360cc85b0f084dedac80de0a" dependencies = [ "logos-codegen", ] @@ -783,11 +784,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", "serde", @@ -804,9 +804,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -842,9 +842,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" @@ -875,9 +875,9 @@ checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -893,9 +893,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", @@ -956,9 +956,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -987,7 +987,7 @@ version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", ] [[package]] @@ -1049,7 +1049,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-syntax", ] [[package]] @@ -1066,15 +1066,9 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.3" @@ -1096,15 +1090,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1123,9 +1117,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -1151,20 +1145,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1287,18 +1281,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.60" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", "unicode-ident", ] @@ -1349,22 +1343,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ - "proc-macro2 1.0.81", + "proc-macro2 1.0.82", "quote 1.0.36", - "syn 2.0.60", + "syn 2.0.63", ] [[package]] @@ -1592,9 +1586,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index 2630d651..77051a3a 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.2.0-beta.0" +version = "0.2.0-beta.1" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -29,7 +29,7 @@ anyhow.workspace = true serde.workspace = true lalrpop-util = "0.20.0" -logos = "0.13" +logos = "0.14" convert_case = "0.6" handlebars = "5.1" toml = { version = "0.8", default-features = false, features = ["parse"] } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index afeb98ff..d136b631 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -387,6 +387,7 @@ impl<'a> State<'a> { res } fn pp_function(&mut self, id: &str, func: &Function) -> Method { + use candid::types::internal::FuncMode; let old = self.state.push_state(&StateElem::Label(id)); let name = self .state @@ -424,7 +425,13 @@ impl<'a> State<'a> { res }) .collect(); - let mode = if func.is_query() { "query" } else { "update" }.to_string(); + let mode = match func.modes.first() { + None => "update", + Some(FuncMode::Query) => "query", + Some(FuncMode::CompositeQuery) => "composite_query", + Some(FuncMode::Oneway) => "update", + } + .to_string(); let res = Method { name, original_name: id.to_string(), @@ -478,19 +485,19 @@ impl<'a> State<'a> { } #[derive(Serialize, Debug)] pub struct Output { - type_defs: String, - methods: Vec, - init_args: Option>, + pub type_defs: String, + pub methods: Vec, + pub init_args: Option>, } #[derive(Serialize, Debug)] pub struct Method { - name: String, - original_name: String, - args: Vec<(String, String)>, - rets: Vec, - mode: String, + pub name: String, + pub original_name: String, + pub args: Vec<(String, String)>, + pub rets: Vec, + pub mode: String, } -pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> Output { +pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Output, Vec) { let (env, actor) = nominalize_all(env, actor); let def_list: Vec<_> = if let Some(actor) = &actor { chase_actor(&env, actor).unwrap() @@ -508,11 +515,15 @@ pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> Outpu } else { (Vec::new(), None) }; - Output { - type_defs: defs.pretty(LINE_WIDTH).to_string(), - methods, - init_args, - } + let unused = state.state.report_unused(); + ( + Output { + type_defs: defs.pretty(LINE_WIDTH).to_string(), + methods, + init_args, + }, + unused, + ) } pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) -> String { let hbs = get_hbs(); @@ -522,11 +533,13 @@ pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) external: BTreeMap, type_defs: String, methods: Vec, + init_args: Option>, } let data = HBOutput { type_defs: output.type_defs, methods: output.methods, external: config.0, + init_args: output.init_args, }; hbs.render_template(template, &data).unwrap() } @@ -536,6 +549,7 @@ impl Config { Self(ConfigTree::from_configs("rust", configs).unwrap()) } } +#[derive(Deserialize)] pub struct ExternalConfig(pub BTreeMap); impl Default for ExternalConfig { fn default() -> Self { @@ -560,9 +574,13 @@ pub fn compile( 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"), + Some("stub") => include_str!("rust_stub.hbs"), _ => unimplemented!(), }; - let output = emit_bindgen(tree, env, actor); + let (output, unused) = emit_bindgen(tree, env, actor); + for e in unused { + eprintln!("WARNING: path {e} is unused"); + } output_handlebar(output, external, source) } @@ -754,7 +772,6 @@ 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(); @@ -853,5 +870,37 @@ fn get_hbs() -> handlebars::Handlebars<'static> { }, ), ); + hbs.register_helper( + "cdk_attribute", + Box::new( + |h: &Helper, + _: &Handlebars, + _: &Context, + _: &mut RenderContext, + out: &mut dyn Output| + -> HelperResult { + let mode = h.param(0).unwrap().value().as_str().unwrap(); + let name = h.param(1).unwrap().value().as_str().unwrap(); + let original_name = h.param(2).unwrap().value().as_str().unwrap(); + if mode == "update" { + out.write("update")?; + } else { + out.write("query")?; + } + let mut attrs = Vec::new(); + if mode == "composite_query" { + attrs.push("composite = true".to_string()); + } + if name != original_name { + attrs.push(format!("name = \"{}\"", original_name.escape_debug())); + } + let attrs = attrs.join(", "); + if !attrs.is_empty() { + out.write(&format!("({attrs})"))?; + } + Ok(()) + }, + ), + ); hbs } diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs index f48fefc6..9fffd9b4 100644 --- a/rust/candid_parser/src/bindings/rust_agent.hbs +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -11,7 +11,7 @@ impl<'a> {{PascalCase service_name}}<'a> { {{#each methods}} 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.original_name}}").with_arg(args).{{#if (eq this.mode "query")}}call{{else}}call_and_wait(){{/if}}.await?; + let bytes = self.1.{{#if (eq this.mode "update")}}update{{else}}query{{/if}}(&self.0, "{{escape_debug this.original_name}}").with_arg(args).{{#if (eq this.mode "update")}}call_and_wait{{else}}call{{/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 6be0fe4b..b12654b6 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -1,7 +1,7 @@ // 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 {{candid_crate}}::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; {{type_defs}} diff --git a/rust/candid_parser/src/bindings/rust_stub.hbs b/rust/candid_parser/src/bindings/rust_stub.hbs new file mode 100644 index 00000000..5a85901a --- /dev/null +++ b/rust/candid_parser/src/bindings/rust_stub.hbs @@ -0,0 +1,18 @@ +// 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}; + +{{type_defs}} +{{#if init_args}} +#[ic_cdk::init] +fn init({{#each init_args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{this.1}}{{/each}}) { + unimplemented!() +} +{{/if}} +{{#each methods}} +#[ic_cdk::{{cdk_attribute this.mode this.name this.original_name}}] +fn {{this.name}}({{#each this.args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{this.1}}{{/each}}) -> {{vec_to_arity this.rets}} { + unimplemented!() +} +{{/each}} diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index c711e4fd..4b4e600b 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -1,13 +1,15 @@ use anyhow::Result; use candid::types::{Type, TypeEnv, TypeInner}; use serde::de::DeserializeOwned; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use toml::{Table, Value}; pub struct State<'a, T: ConfigState> { tree: &'a ConfigTree, - open_tree: Option<&'a ConfigTree>, path: Vec, + open_tree: Option<&'a ConfigTree>, + open_path: Vec, + stats: BTreeMap, u32>, pub config: T, pub env: &'a TypeEnv, } @@ -37,7 +39,9 @@ impl<'a, T: ConfigState> State<'a, T> { Self { tree, open_tree: None, + open_path: Vec::new(), path: Vec::new(), + stats: BTreeMap::new(), config, env, } @@ -45,7 +49,10 @@ impl<'a, T: ConfigState> State<'a, T> { /// Match paths in the scope first. If `scope` is None, clear the scope. pub fn with_scope(&mut self, scope: &Option, idx: usize) { match scope { - None => self.open_tree = None, + None => { + self.open_tree = None; + self.open_path.clear(); + } Some(scope) => { let mut path = vec![format!("method:{}", scope.method)]; match self.tree.with_prefix(&path) { @@ -55,12 +62,24 @@ impl<'a, T: ConfigState> State<'a, T> { Some(ScopePos::Ret) => path.push(format!("ret:{}", idx)), None => (), } - self.open_tree = self.tree.with_prefix(&path).or(Some(tree)); + match self.tree.with_prefix(&path) { + Some(subtree) => { + self.open_tree = Some(subtree); + self.open_path = path; + } + None => { + self.open_tree = Some(tree); + self.open_path = vec![path[0].clone()]; + } + } if let Some(state) = self.open_tree.unwrap().state.as_ref() { self.config.merge_config(state, None, false); } } - None => self.open_tree = None, + None => { + self.open_tree = None; + self.open_path.clear(); + } } } } @@ -70,14 +89,27 @@ impl<'a, T: ConfigState> State<'a, T> { self.config.update_state(elem); let old_config = self.config.clone(); self.path.push(elem.to_string()); + let mut from_open = false; let new_state = if let Some(subtree) = self.open_tree { - subtree - .get_config(&self.path) - .or_else(|| self.tree.get_config(&self.path)) + from_open = true; + subtree.get_config(&self.path).or_else(|| { + from_open = false; + self.tree.get_config(&self.path) + }) } else { self.tree.get_config(&self.path) }; - if let Some((state, is_recursive)) = new_state { + if let Some((state, is_recursive, idx)) = new_state { + let mut matched_path = if from_open { + self.open_path.clone() + } else { + vec![] + }; + matched_path.extend_from_slice(&self.path[idx..]); + self.stats + .entry(matched_path) + .and_modify(|v| *v += 1) + .or_insert(1); self.config.merge_config(state, Some(elem), is_recursive); //eprintln!("match path: {:?}, state: {:?}", self.path, self.config); } else { @@ -92,6 +124,26 @@ impl<'a, T: ConfigState> State<'a, T> { assert_eq!(self.path.pop(), Some(elem.to_string())); self.config.restore_state(&elem); } + pub fn report_unused(&self) -> Vec { + let mut res = BTreeSet::new(); + self.tree.traverse(&mut vec![], &mut res); + for k in self.stats.keys() { + res.remove(k); + } + res.remove(&vec![]); + res.into_iter().map(|v| v.join(".")).collect() + } + pub fn get_stats(mut self) -> BTreeMap { + let mut res = BTreeSet::new(); + self.tree.traverse(&mut vec![], &mut res); + for e in res { + self.stats.entry(e).or_insert(0); + } + self.stats + .into_iter() + .map(|(k, v)| (k.join("."), v)) + .collect() + } } pub trait ConfigState: DeserializeOwned + Default + Clone + std::fmt::Debug { @@ -127,6 +179,7 @@ impl ConfigTree { Some(tree) } pub fn add_config(&mut self, path: &[String], config: T) { + // TODO: correctly count the depth of scoped paths let n = path.len(); let mut tree: &Self = self; let mut i = 0; @@ -165,14 +218,15 @@ impl ConfigTree { tree.max_depth = std::cmp::max(d, tree.max_depth); } } - pub fn get_config(&self, path: &[String]) -> Option<(&T, bool)> { + /// Returns the config, is_recursive, and the index of the matched path + pub fn get_config(&self, path: &[String]) -> Option<(&T, bool, usize)> { 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); match self.match_exact_path(tail) { - Some(v) => return Some((v, is_repeated(path, tail))), + Some(v) => return Some((v, is_repeated(path, tail), i)), None => continue, } } @@ -185,10 +239,31 @@ impl ConfigTree { } result.state.as_ref() } + pub fn traverse(&self, path: &mut Vec, res: &mut BTreeSet>) { + if self.state.is_some() { + res.insert(path.clone()); + } + for (k, v) in self.subtree.iter() { + path.push(k.clone()); + v.traverse(path, res); + path.pop(); + } + } } - +#[derive(Clone)] pub struct Configs(Table); - +impl Configs { + pub fn get_subtable(&self, path: &[String]) -> Option<&Table> { + let mut res = &self.0; + for k in path { + match res.get(k)? { + Value::Table(t) => res = t, + _ => return None, + } + } + Some(res) + } +} impl std::str::FromStr for Configs { type Err = crate::Error; fn from_str(v: &str) -> Result { @@ -205,7 +280,6 @@ impl<'a> std::fmt::Display for StateElem<'a> { } } } - fn is_repeated(path: &[String], matched: &[String]) -> bool { let iter = path.as_ref().windows(matched.len()); for slice in iter { @@ -215,11 +289,9 @@ fn is_repeated(path: &[String], matched: &[String]) -> bool { } false } - fn special_key(key: &str) -> bool { key.starts_with("method:") || key.starts_with("arg:") || key.starts_with("ret:") } - fn generate_state_tree(v: Value) -> Result> { let mut subtree = BTreeMap::new(); let mut leaves = toml::Table::new(); @@ -254,8 +326,7 @@ fn generate_state_tree(v: Value) -> Result> { Err(anyhow::anyhow!("Expected a table")) } } - -pub fn path_name(t: &Type) -> String { +fn path_name(t: &Type) -> String { match t.as_ref() { TypeInner::Null => "null", TypeInner::Bool => "bool", @@ -393,6 +464,7 @@ Vec = { width = 2, size = 10 } }), 0, ); + assert_eq!(state.open_path, vec!["method:f", "arg:0"]); let old = state.push_state(&StateElem::Label("list")); assert_eq!(state.config.depth, Some(2)); assert_eq!(state.config.size, Some(20)); @@ -403,10 +475,11 @@ Vec = { width = 2, size = 10 } state.with_scope( &Some(Scope { method: "f", - position: None, + position: Some(ScopePos::Ret), }), 0, ); + assert_eq!(state.open_path, vec!["method:f"]); state.push_state(&StateElem::Label("list")); assert_eq!(state.config.depth, Some(3)); assert_eq!(state.config.size, Some(0)); @@ -418,4 +491,17 @@ Vec = { width = 2, size = 10 } state.pop_state(old, StateElem::Label("list")); assert_eq!(state.config.size, Some(0)); assert_eq!(state.config.depth, Some(3)); + let stats = state.report_unused(); + assert_eq!( + stats.iter().map(|x| x.as_str()).collect::>(), + [ + "Vec", + "a.b.c", + "a.b.c.d", + "a.b.d", + "left.a", + "left.list", + "vec.nat8" + ] + ); } diff --git a/rust/candid_parser/tests/assets/ok/actor.rs b/rust/candid_parser/tests/assets/ok/actor.rs index 79138a42..d9d385ec 100644 --- a/rust/candid_parser/tests/assets/ok/actor.rs +++ b/rust/candid_parser/tests/assets/ok/actor.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; candid::define_function!(pub F : (i8) -> (i8)); diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index 2198ab57..cfed8194 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] diff --git a/rust/candid_parser/tests/assets/ok/comment.rs b/rust/candid_parser/tests/assets/ok/comment.rs index c819f2f1..958f42fa 100644 --- a/rust/candid_parser/tests/assets/ok/comment.rs +++ b/rust/candid_parser/tests/assets/ok/comment.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; 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 f3fae3f3..314286e9 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.rs +++ b/rust/candid_parser/tests/assets/ok/cyclic.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; pub type C = Box; diff --git a/rust/candid_parser/tests/assets/ok/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index 64d70472..2338c077 100644 --- a/rust/candid_parser/tests/assets/ok/empty.rs +++ b/rust/candid_parser/tests/assets/ok/empty.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] diff --git a/rust/candid_parser/tests/assets/ok/escape.rs b/rust/candid_parser/tests/assets/ok/escape.rs index c6b66fef..776d7e6c 100644 --- a/rust/candid_parser/tests/assets/ok/escape.rs +++ b/rust/candid_parser/tests/assets/ok/escape.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index 18891eb8..e5c735b8 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize, Debug)] diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index 8cbba81a..607c0445 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] diff --git a/rust/candid_parser/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index fd6ce914..f50de12e 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.rs +++ b/rust/candid_parser/tests/assets/ok/keyword.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] diff --git a/rust/candid_parser/tests/assets/ok/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs index 939a8378..91aca069 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.rs +++ b/rust/candid_parser/tests/assets/ok/recursion.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; candid::define_function!(pub T : (S) -> ()); diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.rs b/rust/candid_parser/tests/assets/ok/recursive_class.rs index eda0084e..ce01cd0e 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.rs +++ b/rust/candid_parser/tests/assets/ok/recursive_class.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; candid::define_service!(pub S : { "next" : candid::func!(() -> (S)) }); diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index bdf308f9..631c2095 100644 --- a/rust/candid_parser/tests/assets/ok/service.rs +++ b/rust/candid_parser/tests/assets/ok/service.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; candid::define_function!(pub Func : () -> (Service)); diff --git a/rust/candid_parser/tests/assets/ok/unicode.rs b/rust/candid_parser/tests/assets/ok/unicode.rs index ca077654..ec2ebbe1 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.rs +++ b/rust/candid_parser/tests/assets/ok/unicode.rs @@ -1,7 +1,7 @@ // 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::{self, CandidType, Deserialize, Principal, Encode, Decode}; +use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; #[derive(CandidType, Deserialize)] diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index daeef0ca..c0b59198 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -30,7 +30,7 @@ enum Command { Bind { /// Specifies did file for code generation input: PathBuf, - #[clap(short, long, value_parser = ["js", "ts", "did", "mo", "rs", "rs-agent"])] + #[clap(short, long, value_parser = ["js", "ts", "did", "mo", "rs", "rs-agent", "rs-stub"])] /// Specifies target language target: String, #[clap(short, long)] @@ -217,14 +217,23 @@ fn main() -> Result<()> { "mo" => candid_parser::bindings::motoko::compile(&env, &actor), "rs" => { use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; + let external = configs + .get_subtable(&["external".to_string(), "rust".to_string()]) + .map(|x| x.clone().try_into().unwrap()) + .unwrap_or(ExternalConfig::default()); let config = Config::new(configs); - compile(&config, &env, &actor, ExternalConfig::default()) + compile(&config, &env, &actor, external) } - "rs-agent" => { + "rs-agent" | "rs-stub" => { 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()); + let target = match target.as_str() { + "rs-agent" => "agent", + "rs-stub" => "stub", + _ => unreachable!(), + }; + external.0.insert("target".to_string(), target.to_string()); compile(&config, &env, &actor, external) } _ => unreachable!(),