Skip to content

Commit

Permalink
more rust bindgen (#560)
Browse files Browse the repository at this point in the history
* make configs public

* fix newtype visibility

* add variant.std::record::Record.use_type is rename Result type

* report unused

* no test annotation

* fix

* bump version

* embed metadata

* remove unused types in metadata

* tests
  • Loading branch information
chenyan-dfinity authored Jun 25, 2024
1 parent 258b6b6 commit af33ed8
Show file tree
Hide file tree
Showing 22 changed files with 150 additions and 71 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ thiserror = "1.0"
anyhow = "1.0"
rand = "0.8"
arbitrary = "1.3"
console = "0.15"
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.2"
version = "0.2.0-beta.3"
edition = "2021"
rust-version.workspace = true
authors = ["DFINITY Team"]
Expand Down Expand Up @@ -33,14 +33,14 @@ 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 }
ctrlc = { version = "3.4", optional = true }
console = { workspace = true, optional = true }

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

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

# docs.rs-specific configuration
Expand Down
80 changes: 56 additions & 24 deletions rust/candid_parser/src/bindings/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::{
};
use candid::pretty::utils::*;
use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner};
use console::style;
use convert_case::{Case, Casing};
use pretty::RcDoc;
use serde::Serialize;
Expand Down Expand Up @@ -102,6 +101,13 @@ fn as_result(fs: &[Field]) -> Option<(&Type, &Type)> {
_ => None,
}
}
fn parse_use_type(input: &str) -> (String, bool) {
if let Some((t, "")) = input.rsplit_once("(no test)") {
(t.trim_end().to_string(), false)
} else {
(input.to_string(), true)
}
}
static KEYWORDS: [&str; 51] = [
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for",
"if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
Expand Down Expand Up @@ -176,8 +182,11 @@ fn test_{test_name}() {{
let elem = StateElem::Type(ty);
let old = self.state.push_state(&elem);
let res = if let Some(t) = &self.state.config.use_type {
let res = RcDoc::text(t.clone());
self.generate_test(ty, &t.clone());
let (t, need_test) = parse_use_type(t);
if need_test {
self.generate_test(ty, &t);
}
let res = RcDoc::text(t);
self.state.update_stats("use_type");
res
} else {
Expand Down Expand Up @@ -222,11 +231,28 @@ fn test_{test_name}() {{
Variant(ref fs) => {
// only possible for result variant
let (ok, err) = as_result(fs).unwrap();
let body = self
.pp_ty(ok, is_ref)
.append(", ")
.append(self.pp_ty(err, is_ref));
str("std::result::Result").append(enclose("<", body, ">"))
// This is a hacky way to redirect Result type
let old = self
.state
.push_state(&StateElem::TypeStr("std::result::Result"));
let result = if let Some(t) = &self.state.config.use_type {
let (res, _) = parse_use_type(t);
// not generating test for this use_type. rustc should be able to catch type mismatches.
self.state.update_stats("use_type");
res
} else {
"std::result::Result".to_string()
};
self.state
.pop_state(old, StateElem::TypeStr("std::result::Result"));
let old = self.state.push_state(&StateElem::Label("Ok"));
let ok = self.pp_ty(ok, is_ref);
self.state.pop_state(old, StateElem::Label("Ok"));
let old = self.state.push_state(&StateElem::Label("Err"));
let err = self.pp_ty(err, is_ref);
self.state.pop_state(old, StateElem::Label("Err"));
let body = ok.append(", ").append(err);
RcDoc::text(result).append(enclose("<", body, ">"))
}
Func(_) => unreachable!(), // not possible after rewriting
Service(_) => unreachable!(), // not possible after rewriting
Expand Down Expand Up @@ -305,9 +331,13 @@ fn test_{test_name}() {{
res
}
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 old = if self.state.path.last() == Some(&"record".to_string()) {
// don't push record again when coming from pp_ty
None
} else {
Some(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, is_ref)
} else {
let fields: Vec<_> = fs
Expand All @@ -317,7 +347,9 @@ fn test_{test_name}() {{
let fields = concat(fields.into_iter(), ",");
enclose_space("{", fields, "}")
};
self.state.pop_state(old, StateElem::TypeStr("record"));
if let Some(old) = old {
self.state.pop_state(old, StateElem::TypeStr("record"));
}
res
}
fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> {
Expand Down Expand Up @@ -421,10 +453,11 @@ fn test_{test_name}() {{
if self.recs.contains(id) {
derive
.append(RcDoc::line())
.append(vis)
.append(vis.clone())
.append("struct ")
.append(name)
.append(enclose("(", self.pp_ty(ty, false), ")"))
// TODO: Unfortunately, the visibility of the inner newtype is also controlled by var.visibility
.append(enclose("(", vis.append(self.pp_ty(ty, false)), ")"))
.append(";")
} else {
vis.append(kwd("type"))
Expand Down Expand Up @@ -688,12 +721,18 @@ pub fn compile(
tree: &Config,
env: &TypeEnv,
actor: &Option<Type>,
external: ExternalConfig,
) -> String {
mut external: ExternalConfig,
) -> (String, Vec<String>) {
let source = match external.0.get("target").map(|s| s.as_str()) {
Some("canister_call") | None => Cow::Borrowed(include_str!("rust_call.hbs")),
Some("agent") => Cow::Borrowed(include_str!("rust_agent.hbs")),
Some("stub") => Cow::Borrowed(include_str!("rust_stub.hbs")),
Some("stub") => {
let metadata = crate::utils::get_metadata(env, actor);
if let Some(metadata) = metadata {
external.0.insert("metadata".to_string(), metadata);
}
Cow::Borrowed(include_str!("rust_stub.hbs"))
}
Some("custom") => {
let template = external
.0
Expand All @@ -704,14 +743,7 @@ pub fn compile(
_ => unimplemented!(),
};
let (output, unused) = emit_bindgen(tree, env, actor);
for e in unused {
eprintln!(
"{} path {} is unused",
style("WARNING:").red().bold(),
style(e).green()
);
}
output_handlebar(output, external, &source)
(output_handlebar(output, external, &source), unused)
}

pub enum TypePath {
Expand Down
4 changes: 4 additions & 0 deletions rust/candid_parser/src/bindings/rust_stub.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ fn {{this.name}}({{#each this.args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{t
{{#if tests}}
{{tests}}
{{/if}}
{{#if metadata}}
#[link_section = "icp:public candid:service"]
pub static __SERVICE: [u8; {{len metadata}}] = *br#"{{metadata}}"#;
{{/if}}
2 changes: 1 addition & 1 deletion rust/candid_parser/src/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ impl<T: ConfigState> ConfigTree<T> {
}
}
#[derive(Clone)]
pub struct Configs(Table);
pub struct Configs(pub Table);
impl Configs {
pub fn get_subtable(&self, path: &[String]) -> Option<&Table> {
let mut res = &self.0;
Expand Down
19 changes: 18 additions & 1 deletion rust/candid_parser/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{check_prog, pretty_check_file, pretty_parse, Error, Result};
use candid::types::TypeInner;
use candid::{types::Type, TypeEnv};
use std::path::Path;

Expand Down Expand Up @@ -49,7 +50,6 @@ pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> {
/// If the original did file contains imports, the output flattens the type definitions.
/// For now, the comments from the original did file is omitted.
pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec<Type>, (TypeEnv, Type))> {
use candid::types::TypeInner;
let (env, serv) = candid.load()?;
let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?;
let serv = env.trace_type(&serv)?;
Expand All @@ -59,6 +59,23 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec<Type>, (TypeEnv,
_ => unreachable!(),
})
}
pub fn get_metadata(env: &TypeEnv, serv: &Option<Type>) -> Option<String> {
let serv = serv.clone()?;
let serv = env.trace_type(&serv).ok()?;
let serv = match serv.as_ref() {
TypeInner::Class(_, ty) => ty.clone(),
TypeInner::Service(_) => serv,
_ => unreachable!(),
};
let def_list = crate::bindings::analysis::chase_actor(env, &serv).ok()?;
let mut filtered = TypeEnv::new();
for d in def_list {
if let Some(t) = env.0.get(d) {
filtered.0.insert(d.to_string(), t.clone());
}
}
Some(candid::pretty::candid::compile(&filtered, &Some(serv)))
}

/// Merge canister metadata candid:args and candid:service into a service constructor.
/// If candid:service already contains init args, returns the original did file.
Expand Down
3 changes: 2 additions & 1 deletion rust/candid_parser/tests/assets/class.did
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type Profile = record { age: nat8; name: text };
type List = opt record { int; List };
service : (int, List) -> {
service : (int, List, Profile) -> {
get : () -> (List);
set : (List) -> (List);
}
4 changes: 3 additions & 1 deletion rust/candid_parser/tests/assets/example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ 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<MyList>" }
my_type = { visibility = "", name = "CanisterId" }
nat.use_type = "u128"
nat.use_type = "u128 (no test)"
BrokerFindRet = { name = "BrokerReturn", visibility = "pub" }
g1 = { name = "G11", arg0.name = "id", arg1.name = "list", arg2.name = "is_okay", ret0.use_type = "i128" }
x.ret2.variant.Err.variant.name = "Error"
nested_res.variant.Ok.variant."std::result::Result".use_type = "my::Result"
nested_res.variant.Err.variant."std::result::Result".use_type = "another::Result"
2 changes: 1 addition & 1 deletion rust/candid_parser/tests/assets/ok/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ candid::define_function!(pub F : (i8) -> (i8));
candid::define_function!(pub H : (F) -> (F));
pub type G = F;
#[derive(CandidType, Deserialize)]
pub struct O(Option<Box<O>>);
pub struct O(pub Option<Box<O>>);

pub struct Service(pub Principal);
impl Service {
Expand Down
1 change: 1 addition & 0 deletions rust/candid_parser/tests/assets/ok/class.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ActorMethod } from '@dfinity/agent';
import type { IDL } from '@dfinity/candid';

export type List = [] | [[bigint, List]];
export interface Profile { 'age' : number, 'name' : string }
export interface _SERVICE {
'get' : ActorMethod<[], List>,
'set' : ActorMethod<[List], List>,
Expand Down
3 changes: 2 additions & 1 deletion rust/candid_parser/tests/assets/ok/class.did
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
type List = opt record { int; List };
service : (int, List) -> { get : () -> (List); set : (List) -> (List) }
type Profile = record { age : nat8; name : text };
service : (int, List, Profile) -> { get : () -> (List); set : (List) -> (List) }
4 changes: 3 additions & 1 deletion rust/candid_parser/tests/assets/ok/class.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const idlFactory = ({ IDL }) => {
const List = IDL.Rec();
List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List)));
const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text });
return IDL.Service({
'get' : IDL.Func([], [List], []),
'set' : IDL.Func([List], [List], []),
Expand All @@ -9,5 +10,6 @@ export const idlFactory = ({ IDL }) => {
export const init = ({ IDL }) => {
const List = IDL.Rec();
List.fill(IDL.Opt(IDL.Tuple(IDL.Int, List)));
return [IDL.Int, List];
const Profile = IDL.Record({ 'age' : IDL.Nat8, 'name' : IDL.Text });
return [IDL.Int, List, Profile];
};
3 changes: 2 additions & 1 deletion rust/candid_parser/tests/assets/ok/class.mo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

module {
public type List = ?(Int, List);
public type Self = (Int, List) -> async actor {
public type Profile = { age : Nat8; name : Text };
public type Self = (Int, List, Profile) -> async actor {
get : shared () -> async List;
set : shared List -> async List;
}
Expand Down
29 changes: 17 additions & 12 deletions rust/candid_parser/tests/assets/ok/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
// You may want to manually adjust some of the types.
#![allow(dead_code, unused_imports)]
use candid::{self, CandidType, Deserialize, Principal};
use ic_cdk::api::call::CallResult as Result;

#[derive(CandidType, Deserialize)]
pub struct List(Option<(candid::Int,Box<List>,)>);
pub struct List(pub Option<(candid::Int,Box<List>,)>);
#[derive(CandidType, Deserialize)]
pub struct Profile { pub age: u8, pub name: String }

pub struct Service(pub Principal);
impl Service {
pub async fn get(&self) -> Result<(List,)> {
ic_cdk::call(self.0, "get", ()).await
}
pub async fn set(&self, arg0: List) -> Result<(List,)> {
ic_cdk::call(self.0, "set", (arg0,)).await
}
#[ic_cdk::init]
fn init(arg0: candid::Int, arg1: List, arg2: Profile) {
unimplemented!()
}
#[ic_cdk::update]
fn get() -> List {
unimplemented!()
}
#[ic_cdk::update]
fn set(arg0: List) -> List {
unimplemented!()
}
pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa
pub const service : Service = Service(CANISTER_ID);
#[link_section = "icp:public candid:service"]
pub static __SERVICE: [u8; 94] = *br#"type List = opt record { int; List };
service : { get : () -> (List); set : (List) -> (List) }"#;

2 changes: 1 addition & 1 deletion rust/candid_parser/tests/assets/ok/cyclic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ic_cdk::api::call::CallResult as Result;
pub type C = Box<A>;
pub type B = Option<C>;
#[derive(CandidType, Deserialize)]
pub struct A(Option<B>);
pub struct A(pub Option<B>);
pub type Z = Box<A>;
pub type Y = Z;
pub type X = Y;
Expand Down
Loading

0 comments on commit af33ed8

Please sign in to comment.