Skip to main content

wf_cdk_bindgen/
code_generator.rs

1use candid::pretty::utils::*;
2use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner};
3use candid_parser::bindings::analysis::{chase_actor, infer_rec};
4use convert_case::{Case, Casing};
5use pretty::RcDoc;
6use std::collections::BTreeSet;
7
8#[derive(Clone)]
9pub enum Target {
10    CanisterCall,
11    Agent,
12    CanisterStub,
13    Builder,
14}
15
16#[derive(Clone)]
17pub struct Config {
18    candid_crate: String,
19    type_attributes: String,
20    canister_id: Option<candid::Principal>,
21    canister_wasm_path: Option<String>,
22    service_name: String,
23    target: Target,
24}
25
26impl Config {
27    pub fn new() -> Self {
28        Config {
29            candid_crate: "candid".to_string(),
30            type_attributes: "".to_string(),
31            canister_id: None,
32            canister_wasm_path: None,
33            service_name: "service".to_string(),
34            target: Target::CanisterCall,
35        }
36    }
37    pub fn set_candid_crate(&mut self, name: String) -> &mut Self {
38        self.candid_crate = name;
39        self
40    }
41    /// Applies to all types for now
42    pub fn set_type_attributes(&mut self, attr: String) -> &mut Self {
43        self.type_attributes = attr;
44        self
45    }
46    /// Only generates SERVICE struct if canister_id is not provided
47    pub fn set_canister_id(&mut self, id: candid::Principal) -> &mut Self {
48        self.canister_id = Some(id);
49        self
50    }
51
52    pub fn set_canister_wasm_path(&mut self, wasm_path: String) -> &mut Self {
53        self.canister_wasm_path = Some(wasm_path);
54        self
55    }
56    /// Service name when canister id is provided
57    pub fn set_service_name(&mut self, name: String) -> &mut Self {
58        self.service_name = name;
59        self
60    }
61    pub fn set_target(&mut self, name: Target) -> &mut Self {
62        self.target = name;
63        self
64    }
65}
66impl Default for Config {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72type RecPoints<'a> = BTreeSet<&'a str>;
73// The definition of tuple is language specific.
74pub(crate) fn is_tuple(fs: &[Field]) -> bool {
75    if fs.is_empty() {
76        return false;
77    }
78    !fs.iter()
79        .enumerate()
80        .any(|(i, field)| field.id.get_id() != (i as u32))
81}
82static KEYWORDS: [&str; 51] = [
83    "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for",
84    "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
85    "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where",
86    "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro",
87    "override", "priv", "typeof", "unsized", "virtual", "yield", "try",
88];
89
90fn ident_(id: &str, case: Option<Case>) -> (RcDoc, bool) {
91    if id.is_empty()
92        || id.starts_with(|c: char| !c.is_ascii_alphabetic() && c != '_')
93        || id.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_')
94    {
95        return (RcDoc::text(format!("_{}_", candid::idl_hash(id))), true);
96    }
97    let (is_rename, id) = if let Some(case) = case {
98        let new_id = id.to_case(case);
99        (new_id != id, new_id)
100    } else {
101        (false, id.to_owned())
102    };
103    if ["crate", "self", "super", "Self", "Result", "Principal"].contains(&id.as_str()) {
104        (RcDoc::text(format!("{id}_")), true)
105    } else if KEYWORDS.contains(&id.as_str()) {
106        (RcDoc::text(format!("r#{id}")), is_rename)
107    } else {
108        (RcDoc::text(id), is_rename)
109    }
110}
111
112fn ident(id: &str, case: Option<Case>) -> RcDoc {
113    ident_(id, case).0
114}
115
116fn pp_ty<'a>(ty: &'a Type, recs: &RecPoints) -> RcDoc<'a> {
117    use TypeInner::*;
118    match ty.as_ref() {
119        Null => str("()"),
120        Bool => str("bool"),
121        Nat => str("candid::Nat"),
122        Int => str("candid::Int"),
123        Nat8 => str("u8"),
124        Nat16 => str("u16"),
125        Nat32 => str("u32"),
126        Nat64 => str("u64"),
127        Int8 => str("i8"),
128        Int16 => str("i16"),
129        Int32 => str("i32"),
130        Int64 => str("i64"),
131        Float32 => str("f32"),
132        Float64 => str("f64"),
133        Text => str("String"),
134        Reserved => str("candid::Reserved"),
135        Empty => str("candid::Empty"),
136        Var(ref id) => {
137            let name = ident(id, Some(Case::Pascal));
138            if recs.contains(id.as_str()) {
139                str("Box<").append(name).append(">")
140            } else {
141                name
142            }
143        }
144        Principal => str("Principal"),
145        Opt(ref t) => str("Option").append(enclose("<", pp_ty(t, recs), ">")),
146        // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob`
147        Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"),
148        Vec(ref t) => str("Vec").append(enclose("<", pp_ty(t, recs), ">")),
149        Record(ref fs) => pp_record_fields(fs, recs, ""),
150        Variant(_) => unreachable!(), // not possible after rewriting
151        Func(_) => unreachable!(),    // not possible after rewriting
152        Service(_) => unreachable!(), // not possible after rewriting
153        Class(_, _) => unreachable!(),
154        Knot(_) | Unknown | Future => unreachable!(),
155    }
156}
157
158fn pp_label<'a>(id: &'a SharedLabel, is_variant: bool, vis: &'a str) -> RcDoc<'a> {
159    let vis = if vis.is_empty() {
160        RcDoc::nil()
161    } else {
162        kwd(vis)
163    };
164    match &**id {
165        Label::Named(id) => {
166            let case = if is_variant { Some(Case::Pascal) } else { None };
167            let (doc, is_rename) = ident_(id, case);
168            if is_rename {
169                str("#[serde(rename=\"")
170                    .append(id.escape_debug().to_string())
171                    .append("\")]")
172                    .append(RcDoc::line())
173                    .append(vis)
174                    .append(doc)
175            } else {
176                vis.append(doc)
177            }
178        }
179        Label::Id(n) | Label::Unnamed(n) => vis.append("_").append(RcDoc::as_string(n)).append("_"),
180    }
181}
182
183fn pp_record_field<'a>(field: &'a Field, recs: &RecPoints, vis: &'a str) -> RcDoc<'a> {
184    pp_label(&field.id, false, vis)
185        .append(kwd(":"))
186        .append(pp_ty(&field.ty, recs))
187}
188
189fn pp_record_fields<'a>(fs: &'a [Field], recs: &RecPoints, vis: &'a str) -> RcDoc<'a> {
190    if is_tuple(fs) {
191        let vis = if vis.is_empty() {
192            RcDoc::nil()
193        } else {
194            kwd(vis)
195        };
196        let tuple = RcDoc::concat(
197            fs.iter()
198                .map(|f| vis.clone().append(pp_ty(&f.ty, recs)).append(",")),
199        );
200        enclose("(", tuple, ")")
201    } else {
202        let fields = concat(fs.iter().map(|f| pp_record_field(f, recs, vis)), ",");
203        enclose_space("{", fields, "}")
204    }
205}
206
207fn pp_variant_field<'a>(field: &'a Field, recs: &RecPoints) -> RcDoc<'a> {
208    match field.ty.as_ref() {
209        TypeInner::Null => pp_label(&field.id, true, ""),
210        TypeInner::Record(fs) => {
211            pp_label(&field.id, true, "").append(pp_record_fields(fs, recs, ""))
212        }
213        _ => pp_label(&field.id, true, "").append(enclose("(", pp_ty(&field.ty, recs), ")")),
214    }
215}
216
217fn pp_variant_fields<'a>(fs: &'a [Field], recs: &RecPoints) -> RcDoc<'a> {
218    let fields = concat(fs.iter().map(|f| pp_variant_field(f, recs)), ",");
219    enclose_space("{", fields, "}")
220}
221
222fn pp_defs<'a>(
223    config: &'a Config,
224    env: &'a TypeEnv,
225    def_list: &'a [&'a str],
226    recs: &'a RecPoints,
227) -> RcDoc<'a> {
228    let derive = if config.type_attributes.is_empty() {
229        "#[derive(CandidType, Deserialize)]"
230    } else {
231        &config.type_attributes
232    };
233    lines(def_list.iter().map(|id| {
234        let ty = env.find_type(id).unwrap();
235        let name = ident(id, Some(Case::Pascal)).append(" ");
236        let vis = "pub ";
237        match ty.as_ref() {
238            TypeInner::Record(fs) => {
239                let separator = if is_tuple(fs) {
240                    RcDoc::text(";")
241                } else {
242                    RcDoc::nil()
243                };
244                str(derive)
245                    .append(RcDoc::line())
246                    .append(vis)
247                    .append("struct ")
248                    .append(name)
249                    .append(pp_record_fields(fs, recs, "pub"))
250                    .append(separator)
251                    .append(RcDoc::hardline())
252            }
253            TypeInner::Variant(fs) => str(derive)
254                .append(RcDoc::line())
255                .append(vis)
256                .append("enum ")
257                .append(name)
258                .append(pp_variant_fields(fs, recs))
259                .append(RcDoc::hardline()),
260            TypeInner::Func(func) => str("candid::define_function!(")
261                .append(vis)
262                .append(name)
263                .append(": ")
264                .append(pp_ty_func(func))
265                .append(");"),
266            TypeInner::Service(serv) => str("candid::define_service!(")
267                .append(vis)
268                .append(name)
269                .append(": ")
270                .append(pp_ty_service(serv))
271                .append(");"),
272            _ => {
273                if recs.contains(id) {
274                    str(derive)
275                        .append(RcDoc::line())
276                        .append(vis)
277                        .append("struct ")
278                        .append(ident(id, Some(Case::Pascal)))
279                        .append(enclose("(", pp_ty(ty, recs), ")"))
280                        .append(";")
281                        .append(RcDoc::hardline())
282                } else {
283                    str(vis)
284                        .append(kwd("type"))
285                        .append(name)
286                        .append("= ")
287                        .append(pp_ty(ty, recs))
288                        .append(";")
289                }
290            }
291        }
292    }))
293}
294
295fn pp_args(args: &[Type]) -> RcDoc {
296    let empty = RecPoints::default();
297    let doc = concat(args.iter().map(|t| pp_ty(t, &empty)), ",");
298    enclose("(", doc, ")")
299}
300fn pp_ty_func(f: &Function) -> RcDoc {
301    let args = pp_args(&f.args);
302    let rets = pp_args(&f.rets);
303    let modes = candid::pretty::candid::pp_modes(&f.modes);
304    args.append(" ->")
305        .append(RcDoc::space())
306        .append(rets.append(modes))
307        .nest(INDENT_SPACE)
308}
309fn pp_ty_service(serv: &[(String, Type)]) -> RcDoc {
310    let doc = concat(
311        serv.iter().map(|(id, func)| {
312            let func_doc = match func.as_ref() {
313                TypeInner::Func(ref f) => enclose("candid::func!(", pp_ty_func(f), ")"),
314                TypeInner::Var(_) => pp_ty(func, &RecPoints::default()).append("::ty()"),
315                _ => unreachable!(),
316            };
317            RcDoc::text("\"")
318                .append(id)
319                .append(kwd("\" :"))
320                .append(func_doc)
321        }),
322        ";",
323    );
324    enclose_space("{", doc, "}")
325}
326
327fn pp_function<'a>(config: &Config, id: &'a str, func: &'a Function) -> RcDoc<'a> {
328    let name = ident(id, Some(Case::Snake));
329    let empty = BTreeSet::new();
330
331    let arg_prefix = str(match config.target {
332        Target::CanisterCall => "&self",
333        Target::Agent => "&self",
334        Target::CanisterStub => unimplemented!(),
335        Target::Builder => "&self",
336    });
337    let args = concat(
338        std::iter::once(arg_prefix).chain(func.args.iter().enumerate().map(|(i, ty)| {
339            // TODO: find a way to give argument names
340            RcDoc::as_string(format!("arg{i}: ")).append(pp_ty(ty, &empty))
341        })),
342        ",",
343    );
344    let result = match config.target {
345        Target::CanisterCall => enclose(
346            "Result<(",
347            RcDoc::concat(func.rets.iter().map(|ty| pp_ty(ty, &empty).append(","))),
348            ")>",
349        ),
350        Target::Agent => match func.rets.len() {
351            0 => str("Result<()>"),
352            1 => enclose("Result<(", pp_ty(&func.rets[0], &empty), ")>"),
353            _ => enclose(
354                "Result<(",
355                RcDoc::intersperse(
356                    func.rets.iter().map(|ty| pp_ty(ty, &empty)),
357                    RcDoc::text(", "),
358                ),
359                ")>",
360            ),
361        },
362        Target::CanisterStub => unimplemented!(),
363        Target::Builder => match func.rets.len() {
364            0 => str("super::CallBuilder<()>"),
365            1 => enclose("super::CallBuilder<", pp_ty(&func.rets[0], &empty), ">"),
366            _ => enclose(
367                "super::CallBuilder<(",
368                RcDoc::intersperse(
369                    func.rets.iter().map(|ty| pp_ty(ty, &empty)),
370                    RcDoc::text(", "),
371                ),
372                ")>",
373            ),
374        },
375    };
376    let sig_prefix = match config.target {
377        Target::CanisterCall | Target::Agent | Target::CanisterStub => kwd("pub async fn"),
378        Target::Builder => kwd("pub fn"),
379    };
380    let sig = sig_prefix
381        .append(name)
382        .append(enclose("(", args, ")"))
383        .append(kwd(" ->"))
384        .append(result)
385        .append(RcDoc::space());
386    let method = id.escape_debug().to_string();
387
388    let body = match config.target {
389        Target::CanisterCall => {
390            let args = RcDoc::concat((0..func.args.len()).map(|i| RcDoc::text(format!("arg{i},"))));
391            str("ic_cdk::call(self.0, \"")
392                .append(method)
393                .append("\", ")
394                .append(enclose("(", args, ")"))
395                .append(").await")
396        }
397        Target::Agent => {
398            let is_query = func.is_query();
399            let builder_method = if is_query { "query" } else { "update" };
400            let call = if is_query { "call" } else { "call_and_wait" };
401            let args = RcDoc::intersperse(
402                (0..func.args.len()).map(|i| RcDoc::text(format!("&arg{i}"))),
403                RcDoc::text(", "),
404            );
405            let blob = str("Encode!").append(enclose("(", args, ")?;"));
406            let rets = RcDoc::concat(
407                func.rets
408                    .iter()
409                    .map(|ty| str(", ").append(pp_ty(ty, &empty))),
410            );
411            str("let args = ").append(blob).append(RcDoc::hardline())
412                        .append(format!("let bytes = self.1.{builder_method}(&self.0, \"{method}\").with_arg(args).{call}().await?;"))
413                        .append(RcDoc::hardline())
414                        .append("Ok(Decode!(&bytes").append(rets).append(")?)")
415        }
416        Target::CanisterStub => unimplemented!(),
417        Target::Builder => {
418            let mode = if func.is_query() {
419                "super::CallMode::Query"
420            } else {
421                "super::CallMode::Update"
422            };
423            let args = RcDoc::intersperse(
424                (0..func.args.len()).map(|i| RcDoc::text(format!("&arg{i}"))),
425                RcDoc::text(", "),
426            );
427
428            str("let args = ")
429                .append(str("Encode!").append(enclose("(", args, ");")))
430                .append(RcDoc::hardline())
431                .append(format!(
432                    "self.caller.call(self.canister_id, {mode}, \"{method}\", args)"
433                ))
434        }
435    };
436
437    sig.append(enclose_space("{", body, "}"))
438}
439
440fn pp_actor<'a>(config: &'a Config, env: &'a TypeEnv, actor: &'a Type) -> RcDoc<'a> {
441    // TODO trace to service before we figure out what canister means in Rust
442
443    let init_args = if let TypeInner::Class(args, _) = actor.as_ref() {
444        Some(args)
445    } else {
446        None
447    };
448
449    let serv = env.as_service(actor).unwrap();
450    let body = RcDoc::intersperse(
451        serv.iter().map(|(id, func)| {
452            let func = env.as_func(func).unwrap();
453            pp_function(config, id, func)
454        }),
455        RcDoc::hardline(),
456    );
457    let struct_name = config.service_name.to_case(Case::Pascal);
458
459    let service_def_prefix = RcDoc::text(format!("pub struct {struct_name} "));
460
461    let service_def_body = match config.target {
462        Target::CanisterCall => str("(pub Principal);"),
463        Target::Agent => str("(pub Principal, pub &'a ic_agent::Agent);"),
464        Target::CanisterStub => unimplemented!(),
465        Target::Builder => enclose_space(
466            "{",
467            RcDoc::intersperse(
468                [
469                    str("pub canister_id: Principal"),
470                    str("pub caller: super::Caller"),
471                ],
472                str(",").append(RcDoc::hardline()),
473            ),
474            "}",
475        ),
476    };
477
478    let service_def = service_def_prefix
479        .append(service_def_body)
480        .append(RcDoc::hardline());
481
482    let service_impl = match config.target {
483        Target::CanisterCall => format!("impl {struct_name} "),
484        Target::Agent => format!("impl<'a> {struct_name}<'a> "),
485        Target::CanisterStub => unimplemented!(),
486        Target::Builder => format!("impl {struct_name} "),
487    };
488    let res = service_def
489        .append(RcDoc::hardline())
490        .append(service_impl)
491        .append(enclose_space("{", body, "}"))
492        .append(RcDoc::hardline());
493    let res = if let Some(cid) = config.canister_id {
494        let slice = cid
495            .as_slice()
496            .iter()
497            .map(|b| b.to_string())
498            .collect::<Vec<_>>()
499            .join(", ");
500        let id = RcDoc::text(format!(
501            "pub const CANISTER_ID : Principal = Principal::from_slice(&[{slice}]); // {cid}"
502        ));
503        let instance = match config.target {
504            Target::CanisterCall => format!(
505                "pub const {} : {} = {}(CANISTER_ID);",
506                config.service_name, struct_name, struct_name
507            ),
508            Target::Agent => "".to_string(),
509            Target::CanisterStub => unimplemented!(),
510            Target::Builder => "".to_string(),
511        };
512        res.append(id).append(RcDoc::hardline()).append(instance)
513    } else {
514        res
515    };
516
517    res.append(pp_actor_new(config, struct_name.clone()))
518        .append(pp_actor_deploy(config, struct_name, init_args))
519        .append(pp_actor_canister_id(config))
520        .append(pp_actor_wasm(config))
521}
522
523pub fn pp_actor_new<'a>(config: &'a Config, struct_name: String) -> RcDoc<'a> {
524    match config.target {
525        Target::CanisterCall | Target::Agent | Target::CanisterStub => RcDoc::nil(),
526        Target::Builder => {
527            let args = RcDoc::intersperse(
528                [str("caller: &super::Caller"), str("canister_id: Principal")],
529                ", ",
530            );
531            let body = RcDoc::text(struct_name.clone())
532                .append(RcDoc::space())
533                .append(enclose_space(
534                    "{",
535                    RcDoc::intersperse(
536                        [str("canister_id"), str("caller: caller.clone()")],
537                        str(",").append(RcDoc::line()),
538                    ),
539                    "}",
540                ));
541            let result = enclose("pub fn new(", args, ")")
542                .append(format!("-> {struct_name}"))
543                .append(RcDoc::space())
544                .append(enclose_space("{", body, "}"))
545                .append(RcDoc::hardline());
546            RcDoc::hardline().append(result)
547        }
548    }
549}
550
551pub fn pp_actor_deploy<'a>(
552    config: &'a Config,
553    struct_name: String,
554    init_args: Option<&'a Vec<Type>>,
555) -> RcDoc<'a> {
556    let empty = BTreeSet::new();
557
558    match config.target {
559        Target::CanisterCall | Target::Agent | Target::CanisterStub => RcDoc::nil(),
560        Target::Builder => {
561            let mut args = str("deployer: &super::Deployer");
562
563            if let Some(init_args) = init_args {
564                args = RcDoc::intersperse(
565                    std::iter::once(args).chain(init_args.iter().enumerate().map(|(i, ty)| {
566                        RcDoc::as_string(format!("arg{i}: ")).append(pp_ty(ty, &empty))
567                    })),
568                    ", ",
569                );
570            }
571
572            let sig = enclose("pub fn deploy(", args, ")")
573                .append(format!("-> super::DeployBuilder<{struct_name}>"))
574                .append(RcDoc::space());
575
576            let args = if let Some(init_args) = init_args {
577                RcDoc::intersperse(
578                    (0..init_args.len()).map(|i| RcDoc::text(format!("&arg{i}"))),
579                    RcDoc::text(", "),
580                )
581            } else {
582                str("")
583            };
584
585            let body = str("let args = ")
586                .append(str("Encode!").append(enclose("(", args, ");")))
587                .append(RcDoc::hardline())
588                .append(str("let result = "))
589                .append(enclose(
590                    "deployer.deploy(",
591                    RcDoc::intersperse([str("args"), str("new")], ", "),
592                    ");",
593                ))
594                .append(RcDoc::hardline())
595                .append(str("let result = if let Some(id) = canister_id()"))
596                .append(enclose("{", str("result.with_canister_id(id)"), "}"))
597                .append(enclose("else {", str("result"), "};"))
598                .append(RcDoc::hardline())
599                .append(str("if let Some(wasm) = wasm()"))
600                .append(enclose("{", str("result.with_wasm(wasm)"), "}"))
601                .append(enclose("else {", str("result"), "}"));
602
603            let result = sig
604                .append(RcDoc::hardline())
605                .append(enclose_space("{", body, "}"));
606
607            RcDoc::hardline().append(result)
608        }
609    }
610}
611
612pub fn pp_actor_canister_id<'a>(config: &'a Config) -> RcDoc<'a> {
613    match config.target {
614        Target::CanisterCall | Target::Agent | Target::CanisterStub => RcDoc::nil(),
615        Target::Builder => {
616            let body = if let Some(canister_id) = config.canister_id {
617                RcDoc::text(format!(
618                    "Some(Principal::from_text(\"{canister_id}\").unwrap())"
619                ))
620            } else {
621                str("None")
622            };
623
624            let result = str("pub fn canister_id() -> Option<Principal>")
625                .append(RcDoc::space())
626                .append(enclose_space("{", body, "}"))
627                .append(RcDoc::hardline());
628            RcDoc::hardline().append(result)
629        }
630    }
631}
632
633pub fn pp_actor_wasm<'a>(config: &'a Config) -> RcDoc<'a> {
634    match config.target {
635        Target::CanisterCall | Target::Agent | Target::CanisterStub => RcDoc::nil(),
636        Target::Builder => {
637            let body = if let Some(wasm_path) = config.canister_wasm_path.clone() {
638                let path = if let Some(wasm_path) = wasm_path.strip_prefix("$HOME") {
639                    str("let mut path = std::path::PathBuf::new();")
640                        .append(str("path.push(std::env::var(\"HOME\").unwrap());"))
641                        .append(RcDoc::text(format!("path.push(\"{wasm_path}\");")))
642                } else {
643                    str("let mut path = std::path::PathBuf::new();")
644                        .append(RcDoc::text(format!("path.push(\"{wasm_path}\");")))
645                };
646
647                path.append(str("let wasm = std::fs::read(path.as_path())"))
648                    .append(".unwrap_or_else")
649                    .append(enclose(
650                        "(",
651                        str("|_| panic!(\"wasm binary not found: {path:?}\")"),
652                        ");",
653                    ))
654                    .append(str("Some(wasm)"))
655            } else {
656                str("None")
657            };
658            let result = str("pub fn wasm() -> Option<Vec<u8>>")
659                .append(RcDoc::space())
660                .append(enclose_space("{", body, "}"))
661                .append(RcDoc::hardline());
662            RcDoc::hardline().append(result)
663        }
664    }
665}
666
667pub fn compile(config: &Config, env: &TypeEnv, actor: &Option<Type>) -> String {
668    let header = format!(
669        r#"// This is an experimental feature used to generate Rust bindings from Candid.
670// THIS IS A GENERATED FILE. DO NOT EDIT THIS FILE TO AVOID DATA LOSS.
671#![allow(dead_code, unused_imports, non_snake_case)]
672use {}::{{self, CandidType, Deserialize, Principal, Encode, Decode}};
673"#,
674        config.candid_crate
675    );
676    let header = header
677        + match &config.target {
678            Target::CanisterCall => "use ic_cdk::api::call::CallResult as Result;\n",
679            Target::Agent => "type Result<T> = std::result::Result<T, ic_agent::AgentError>;\n",
680            Target::CanisterStub => "",
681            Target::Builder => "",
682        };
683    let (env, actor) = nominalize_all(env, actor);
684    let def_list: Vec<_> = if let Some(actor) = &actor {
685        chase_actor(&env, actor).unwrap()
686    } else {
687        env.0.iter().map(|pair| pair.0.as_ref()).collect()
688    };
689    let recs = infer_rec(&env, &def_list).unwrap();
690    let defs = pp_defs(config, &env, &def_list, &recs);
691    let doc = match &actor {
692        None => defs,
693        Some(actor) => {
694            let actor = pp_actor(config, &env, actor);
695            defs.append(actor)
696        }
697    };
698    let doc = RcDoc::text(header).append(RcDoc::line()).append(doc);
699    doc.pretty(LINE_WIDTH).to_string()
700}
701
702pub enum TypePath {
703    Id(String),
704    Opt,
705    Vec,
706    RecordField(String),
707    VariantField(String),
708    Func(String),
709    Init,
710}
711fn path_to_var(path: &[TypePath]) -> String {
712    let name: Vec<&str> = path
713        .iter()
714        .map(|node| match node {
715            TypePath::Id(id) => id.as_str(),
716            TypePath::RecordField(f) | TypePath::VariantField(f) => f.as_str(),
717            TypePath::Opt => "inner",
718            TypePath::Vec => "item",
719            TypePath::Func(id) => id.as_str(),
720            TypePath::Init => "init",
721        })
722        .collect();
723    name.join("_").to_case(Case::Pascal)
724}
725// Convert structural typing to nominal typing to fit Rust's type system
726fn nominalize(env: &mut TypeEnv, path: &mut Vec<TypePath>, t: &Type) -> Type {
727    match t.as_ref() {
728        TypeInner::Opt(ty) => {
729            path.push(TypePath::Opt);
730            let ty = nominalize(env, path, ty);
731            path.pop();
732            TypeInner::Opt(ty)
733        }
734        TypeInner::Vec(ty) => {
735            path.push(TypePath::Vec);
736            let ty = nominalize(env, path, ty);
737            path.pop();
738            TypeInner::Vec(ty)
739        }
740        TypeInner::Record(fs) => {
741            if matches!(
742                path.last(),
743                None | Some(TypePath::VariantField(_)) | Some(TypePath::Id(_))
744            ) || is_tuple(fs)
745            {
746                let fs: Vec<_> = fs
747                    .iter()
748                    .map(|Field { id, ty }| {
749                        path.push(TypePath::RecordField(id.to_string()));
750                        let ty = nominalize(env, path, ty);
751                        path.pop();
752                        Field { id: id.clone(), ty }
753                    })
754                    .collect();
755                TypeInner::Record(fs)
756            } else {
757                let new_var = path_to_var(path);
758                let ty = nominalize(
759                    env,
760                    &mut vec![TypePath::Id(new_var.clone())],
761                    &TypeInner::Record(fs.to_vec()).into(),
762                );
763                env.0.insert(new_var.clone(), ty);
764                TypeInner::Var(new_var)
765            }
766        }
767        TypeInner::Variant(fs) => match path.last() {
768            None | Some(TypePath::Id(_)) => {
769                let fs: Vec<_> = fs
770                    .iter()
771                    .map(|Field { id, ty }| {
772                        path.push(TypePath::VariantField(id.to_string()));
773                        let ty = nominalize(env, path, ty);
774                        path.pop();
775                        Field { id: id.clone(), ty }
776                    })
777                    .collect();
778                TypeInner::Variant(fs)
779            }
780            Some(_) => {
781                let new_var = path_to_var(path);
782                let ty = nominalize(
783                    env,
784                    &mut vec![TypePath::Id(new_var.clone())],
785                    &TypeInner::Variant(fs.to_vec()).into(),
786                );
787                env.0.insert(new_var.clone(), ty);
788                TypeInner::Var(new_var)
789            }
790        },
791        TypeInner::Func(func) => match path.last() {
792            None | Some(TypePath::Id(_)) => {
793                let func = func.clone();
794                TypeInner::Func(Function {
795                    modes: func.modes,
796                    args: func
797                        .args
798                        .into_iter()
799                        .enumerate()
800                        .map(|(i, ty)| {
801                            let i = if i == 0 {
802                                "".to_string()
803                            } else {
804                                i.to_string()
805                            };
806                            path.push(TypePath::Func(format!("arg{i}")));
807                            let ty = nominalize(env, path, &ty);
808                            path.pop();
809                            ty
810                        })
811                        .collect(),
812                    rets: func
813                        .rets
814                        .into_iter()
815                        .enumerate()
816                        .map(|(i, ty)| {
817                            let i = if i == 0 {
818                                "".to_string()
819                            } else {
820                                i.to_string()
821                            };
822                            path.push(TypePath::Func(format!("ret{i}")));
823                            let ty = nominalize(env, path, &ty);
824                            path.pop();
825                            ty
826                        })
827                        .collect(),
828                })
829            }
830            Some(_) => {
831                let new_var = path_to_var(path);
832                let ty = nominalize(
833                    env,
834                    &mut vec![TypePath::Id(new_var.clone())],
835                    &TypeInner::Func(func.clone()).into(),
836                );
837                env.0.insert(new_var.clone(), ty);
838                TypeInner::Var(new_var)
839            }
840        },
841        TypeInner::Service(serv) => match path.last() {
842            None | Some(TypePath::Id(_)) => TypeInner::Service(
843                serv.iter()
844                    .map(|(meth, ty)| {
845                        path.push(TypePath::Id(meth.to_string()));
846                        let ty = nominalize(env, path, ty);
847                        path.pop();
848                        (meth.clone(), ty)
849                    })
850                    .collect(),
851            ),
852            Some(_) => {
853                let new_var = path_to_var(path);
854                let ty = nominalize(
855                    env,
856                    &mut vec![TypePath::Id(new_var.clone())],
857                    &TypeInner::Service(serv.clone()).into(),
858                );
859                env.0.insert(new_var.clone(), ty);
860                TypeInner::Var(new_var)
861            }
862        },
863        TypeInner::Class(args, ty) => TypeInner::Class(
864            args.iter()
865                .map(|ty| {
866                    path.push(TypePath::Init);
867                    let ty = nominalize(env, path, ty);
868                    path.pop();
869                    ty
870                })
871                .collect(),
872            nominalize(env, path, ty),
873        ),
874        _ => return t.clone(),
875    }
876    .into()
877}
878
879fn nominalize_all(env: &TypeEnv, actor: &Option<Type>) -> (TypeEnv, Option<Type>) {
880    let mut res = TypeEnv(Default::default());
881    for (id, ty) in env.0.iter() {
882        let ty = nominalize(&mut res, &mut vec![TypePath::Id(id.clone())], ty);
883        res.0.insert(id.to_string(), ty);
884    }
885    let actor = actor
886        .as_ref()
887        .map(|ty| nominalize(&mut res, &mut vec![], ty));
888    (res, actor)
889}