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 pub fn set_type_attributes(&mut self, attr: String) -> &mut Self {
43 self.type_attributes = attr;
44 self
45 }
46 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 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>;
73pub(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 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!(), Func(_) => unreachable!(), Service(_) => unreachable!(), 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 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 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}
725fn 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}