Skip to content

Commit

Permalink
more improvements for Rust bindgen (#558)
Browse files Browse the repository at this point in the history
* 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

* add def_use analysis

* add custom target for didc

* readme

* generate only subset of bindings

* s/method/func/

* checkpoint

* checkpoint

* checkpoint

* use context

* fix

* add config_source

* check unused at the property level

* no scoped path

* specialize result type

* fix

* prompt color

* add use_type test

* update tests

* bump version

* fix
  • Loading branch information
chenyan-dfinity committed Jun 20, 2024
1 parent 66cf688 commit 223fe03
Show file tree
Hide file tree
Showing 22 changed files with 787 additions and 270 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 6 additions & 11 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,25 @@

## 2024-05-03

### candid_parser 0.2.0-beta.0
### candid_parser 0.2.0-beta

* Breaking changes:
+ Rewrite `configs` and `random` modules, adapting TOML format as the parser. `configs` module is no longer under a feature flag, and no longer depend on dhall.
+ Rewrite Rust bindgen to use the new `configs` module. Use `emit_bindgen` to generate type bindings, and use `output_handlebar` to provide a handlebar template for the generated module. `compile` function provides a default template. The generated file without config is exactly the same as before.

* TO-DOs
+ Spec for path and matching semantics
+ Warning for unused path
+ Rust bindgen:
* Generate `use_type` tests. How to handle recursive types?
* Threading state through `nominalize`
* When the path starts with method, duplicate type definition when necessary.
* Number label renaming
* Non-breaking changes:
+ `utils::check_rust_type` function to check if a Rust type implements the provided candid type.

## 2024-04-11

### Candid 0.10.5 -- 0.10.8
### Candid 0.10.5 -- 0.10.9

* Switch `HashMap` to `BTreeMap` in serialization and `T::ty()`. This leads to around 20% perf improvement for serializing complicated types.
* Disable memoization for unrolled types in serialization to save cycle cost. In some cases, type table can get slightly larger, but it's worth the trade off.
* Fix bug in `text_size`
* Fix decoding cost calculation overflow
* Fix length check in decoding principal type
* Implement `CandidType` for `serde_bytes::ByteArray`
* Add `pretty::candid::pp_init_args` function to pretty print init args

## 2024-02-27

Expand Down
3 changes: 2 additions & 1 deletion rust/candid/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "candid"
version = "0.10.8"
version = "0.10.9"
edition = "2021"
rust-version.workspace = true
authors = ["DFINITY Team"]
Expand Down Expand Up @@ -39,6 +39,7 @@ rand.workspace = true
serde_cbor = "0.11.2"
serde_json = "1.0.74"
bincode = "1.3.3"
candid_parser = { path = "../candid_parser" }

[features]
bignum = ["dep:num-bigint", "dep:num-traits"]
Expand Down
3 changes: 3 additions & 0 deletions rust/candid/src/pretty/candid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ fn pp_actor(ty: &Type) -> RcDoc {
}
}

pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [Type]) -> RcDoc<'a> {
pp_defs(env).append(pp_args(args))
}
pub fn compile(env: &TypeEnv, actor: &Option<Type>) -> String {
match actor {
None => pp_defs(env).pretty(LINE_WIDTH).to_string(),
Expand Down
18 changes: 17 additions & 1 deletion rust/candid/tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use candid::{
decode_one_with_config, encode_one, CandidType, Decode, DecoderConfig, Deserialize, Encode,
Int, Nat,
};
use candid_parser::utils::check_rust_type;

#[test]
fn test_error() {
Expand Down Expand Up @@ -263,6 +264,8 @@ fn test_struct() {
// with memoization on the unrolled type, type table will have 2 entries.
// all_check(list, "4449444c026e016c02a0d2aca8047c90eddae70400010000");
all_check(list, "4449444c036e016c02a0d2aca8047c90eddae704026e01010000");
check_rust_type::<List>("type List = record {head: int; tail: opt List}; (List)").unwrap();
check_rust_type::<List>("type T = record {head: int; tail: opt T}; (T)").unwrap();
}

#[test]
Expand All @@ -289,6 +292,10 @@ fn optional_fields() {
bar: bool,
baz: Option<New>,
}
check_rust_type::<NewStruct>(
"(record { foo: opt nat8; bar: bool; baz: opt variant { Foo; Bar: bool; Baz: bool }})",
)
.unwrap();
let bytes = encode(&OldStruct {
bar: true,
baz: Some(Old::Foo),
Expand Down Expand Up @@ -334,6 +341,13 @@ fn test_equivalent_types() {
struct TypeB {
typea: Option<TypeA>,
}
check_rust_type::<RootType>(
r#"
type A = record { typeb: opt B };
type B = record { typea: opt A };
(record { typeas: vec A })"#,
)
.unwrap();
// Encode to the following types leads to equivalent but different representations of TypeA
all_check(
RootType { typeas: Vec::new() },
Expand Down Expand Up @@ -569,6 +583,7 @@ fn test_tuple() {
(Int::from(42), "💩".to_string()),
"4449444c016c02007c017101002a04f09f92a9",
);
check_rust_type::<(Int, String)>("(record {int; text})").unwrap();
let none: Option<String> = None;
let bytes =
hex("4449444c046c04007c017e020103026d7c6e036c02a0d2aca8047c90eddae7040201002b010302030400");
Expand Down Expand Up @@ -677,6 +692,7 @@ fn test_field_rename() {
#[serde(rename = "a-b")]
B(u8),
}
check_rust_type::<E2>(r#"(variant { "1-2 + 3": int8; "a-b": nat8 })"#).unwrap();
all_check(E2::A(42), "4449444c016b02b684a7027bb493ee970d770100012a");
#[derive(CandidType, Deserialize, PartialEq, Debug)]
struct S2 {
Expand All @@ -698,7 +714,7 @@ fn test_generics() {
g1: T,
g2: E,
}

check_rust_type::<G<i32, bool>>("(record {g1: int32; g2: bool})").unwrap();
let res = G {
g1: Int::from(42),
g2: true,
Expand Down
6 changes: 3 additions & 3 deletions rust/candid_parser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "candid_parser"
version = "0.2.0-beta.1"
version = "0.2.0-beta.2"
edition = "2021"
rust-version.workspace = true
authors = ["DFINITY Team"]
Expand Down Expand Up @@ -33,13 +33,13 @@ logos = "0.14"
convert_case = "0.6"
handlebars = "5.1"
toml = { version = "0.8", default-features = false, features = ["parse"] }
console = "0.15"

arbitrary = { workspace = true, optional = true }
fake = { version = "2.4", optional = true }
rand = { version = "0.8", optional = true }
num-traits = { 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 }

[dev-dependencies]
Expand All @@ -49,7 +49,7 @@ rand.workspace = true

[features]
random = ["dep:arbitrary", "dep:fake", "dep:rand", "dep:num-traits"]
assist = ["dep:dialoguer", "dep:console", "dep:ctrlc"]
assist = ["dep:dialoguer", "dep:ctrlc"]
all = ["random", "assist"]

# docs.rs-specific configuration
Expand Down
54 changes: 53 additions & 1 deletion rust/candid_parser/src/bindings/analysis.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
use crate::Result;
use candid::types::{Type, TypeEnv, TypeInner};
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};

/// Select a subset of methods from an actor.
pub fn project_methods(env: &TypeEnv, actor: &Option<Type>, methods: &[String]) -> Option<Type> {
let service = env.as_service(actor.as_ref()?).ok()?;
let filtered = service
.iter()
.filter(|(name, _)| methods.contains(name))
.cloned()
.collect();
Some(TypeInner::Service(filtered).into())
}

/// Same as chase_actor, with seen set as part of the type. Used for chasing type names from type definitions.
pub fn chase_type<'a>(
Expand Down Expand Up @@ -53,6 +64,47 @@ pub fn chase_actor<'a>(env: &'a TypeEnv, actor: &'a Type) -> Result<Vec<&'a str>
chase_type(&mut seen, &mut res, env, actor)?;
Ok(res)
}
/// Given an actor, return a map from variable names to the (methods, arg) that use them.
pub fn chase_def_use<'a>(
env: &'a TypeEnv,
actor: &'a Type,
) -> Result<BTreeMap<String, Vec<String>>> {
let mut res = BTreeMap::new();
let actor = env.trace_type(actor)?;
if let TypeInner::Class(args, _) = actor.as_ref() {
for (i, arg) in args.iter().enumerate() {
let mut used = Vec::new();
chase_type(&mut BTreeSet::new(), &mut used, env, arg)?;
for var in used {
res.entry(var.to_string())
.or_insert_with(Vec::new)
.push(format!("init.arg{}", i));
}
}
}
for (id, ty) in env.as_service(&actor)? {
let func = env.as_func(ty)?;
for (i, arg) in func.args.iter().enumerate() {
let mut used = Vec::new();
chase_type(&mut BTreeSet::new(), &mut used, env, arg)?;
for var in used {
res.entry(var.to_string())
.or_insert_with(Vec::new)
.push(format!("{}.arg{}", id, i));
}
}
for (i, arg) in func.rets.iter().enumerate() {
let mut used = Vec::new();
chase_type(&mut BTreeSet::new(), &mut used, env, arg)?;
for var in used {
res.entry(var.to_string())
.or_insert_with(Vec::new)
.push(format!("{}.ret{}", id, i));
}
}
}
Ok(res)
}

pub fn chase_types<'a>(env: &'a TypeEnv, tys: &'a [Type]) -> Result<Vec<&'a str>> {
let mut seen = BTreeSet::new();
Expand Down
Loading

0 comments on commit 223fe03

Please sign in to comment.