diff --git a/CHANGELOG.md b/CHANGELOG.md index 884c2565e..94635df22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ - `Binding` - `IDLProg` - `IDLInitArgs` + + The `IDLMergedProg` struct has been added to support the parsing and bindings generation, to avoid using the `TypeEnv`. + + The IDL types can now be converted to `Type` and using the `From` trait. + + The `TypeEnv` struct now has a `as_idl_type` method to convert `Type` to `IDLType`. ### candid_parser @@ -43,6 +46,10 @@ You must now use the `parse_idl_prog`, `parse_idl_init_args`, `parse_idl_type` and `parse_idl_types` functions to parse these types, respectively. + `pretty_parse` doesn't work anymore with the `IDLProg` and `IDLTypes` types. Use `pretty_parse_idl_prog` and `pretty_parse_idl_types` instead. + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The bindings generators now use the IDL merged prog and types. + + `check_file` and `pretty_check_file` now return a `(TypeEnv, Option, IDLMergedProg)` tuple, instead of `(TypeEnv, Option)`. Same for the `CandidSource.load` method. + + `utils::get_metadata` now accepts only an `&IDLMergedProg` parameter. + + The `chase_type`, `chase_actor`, `chase_def_use`, `chase_types` and `infer_rec` functions of the `bindings::analysis` module now accept an IDL merged prog and types as parameters. * Non-breaking changes: + Supports parsing the arguments' names for `func` and `service` (init args). diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 5f6e0272d..0e727fcc0 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -212,7 +212,7 @@ fn nns() -> BenchResult { "#; bench_fn(|| { let _p = bench_scope("0. Parsing"); - let (env, serv) = nns_did.load().unwrap(); + let (env, serv, _) = nns_did.load().unwrap(); let args = candid_parser::parse_idl_args(motion_proposal).unwrap(); let serv = serv.unwrap(); let method = &env.get_method(&serv, "manage_neuron").unwrap(); diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 0f3b80ed2..62cc4f60e 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -1,6 +1,7 @@ use crate::pretty::utils::*; use crate::types::{ - ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner, + syntax::{Binding, FuncType, IDLArgType, IDLMergedProg, IDLType, PrimType, TypeField}, + FuncMode, Label, }; use pretty::RcDoc; @@ -73,12 +74,8 @@ pub(crate) fn pp_text(id: &str) -> RcDoc { RcDoc::text(ident_string(id)) } -pub fn pp_ty(ty: &Type) -> RcDoc { - pp_ty_inner(ty.as_ref()) -} - -pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { - use TypeInner::*; +fn pp_prim_ty(ty: &PrimType) -> RcDoc { + use PrimType::*; match ty { Null => str("null"), Bool => str("bool"), @@ -97,59 +94,62 @@ pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { Text => str("text"), Reserved => str("reserved"), Empty => str("empty"), - Var(ref s) => str(s), - Principal => str("principal"), - Opt(ref t) => kwd("opt").append(pp_ty(t)), - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("blob"), - Vec(ref t) => kwd("vec").append(pp_ty(t)), - Record(ref fs) => { - let t = Type(ty.clone().into()); - if t.is_tuple() { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ";"); + } +} + +pub fn pp_ty(ty: &IDLType) -> RcDoc { + use IDLType::*; + match ty { + PrimT(ty) => pp_prim_ty(ty), + VarT(ref s) => str(s), + PrincipalT => str("principal"), + OptT(ref t) => kwd("opt").append(pp_ty(t)), + VecT(ref t) if matches!(t.as_ref(), PrimT(PrimType::Nat8)) => str("blob"), + VecT(ref t) => kwd("vec").append(pp_ty(t)), + RecordT(ref fs) => { + if ty.is_tuple() { + let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ";"); kwd("record").append(enclose_space("{", tuple, "}")) } else { kwd("record").append(pp_fields(fs, false)) } } - Variant(ref fs) => kwd("variant").append(pp_fields(fs, true)), - Func(ref func) => kwd("func").append(pp_function(func)), - Service(ref serv) => kwd("service").append(pp_service(serv)), - Class(ref args, ref t) => { + VariantT(ref fs) => kwd("variant").append(pp_fields(fs, true)), + FuncT(ref func) => kwd("func").append(pp_function(func)), + ServT(ref serv) => kwd("service").append(pp_service(serv)), + ClassT(ref args, ref t) => { let doc = pp_args(args).append(" ->").append(RcDoc::space()); match t.as_ref() { - Service(ref serv) => doc.append(pp_service(serv)), - Var(ref s) => doc.append(s), + IDLType::ServT(ref serv) => doc.append(pp_service(serv)), + IDLType::VarT(ref s) => doc.append(s), _ => unreachable!(), } } - Knot(ref id) => RcDoc::text(format!("{id}")), - Unknown => str("unknown"), - Future => str("future"), } } -pub fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +pub fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(id) => pp_text(id), Label::Id(_) | Label::Unnamed(_) => RcDoc::as_string(id), } } -pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { - let ty_doc = if is_variant && *field.ty == TypeInner::Null { +pub(crate) fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc { + let ty_doc = if is_variant && field.typ.is_null() { RcDoc::nil() } else { - kwd(" :").append(pp_ty(&field.ty)) + kwd(" :").append(pp_ty(&field.typ)) }; - pp_label(&field.id).append(ty_doc) + pp_label(&field.label).append(ty_doc) } -fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc { +fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc { let fields = concat(fs.iter().map(|f| pp_field(f, is_variant)), ";"); enclose_space("{", fields, "}") } -pub fn pp_function(func: &Function) -> RcDoc { +pub fn pp_function(func: &FuncType) -> RcDoc { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -159,7 +159,7 @@ pub fn pp_function(func: &Function) -> RcDoc { .nest(INDENT_SPACE) } -pub fn pp_args(args: &[ArgType]) -> RcDoc { +pub fn pp_args(args: &[IDLArgType]) -> RcDoc { let args = args.iter().map(|arg| { if let Some(name) = &arg.name { pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ)) @@ -171,7 +171,7 @@ pub fn pp_args(args: &[ArgType]) -> RcDoc { enclose("(", doc, ")") } -pub fn pp_rets(rets: &[Type]) -> RcDoc { +pub fn pp_rets(rets: &[IDLType]) -> RcDoc { let doc = concat(rets.iter().map(pp_ty), ","); enclose("(", doc, ")") } @@ -187,12 +187,12 @@ pub fn pp_modes(modes: &[FuncMode]) -> RcDoc { RcDoc::concat(modes.iter().map(|m| RcDoc::space().append(pp_mode(m)))) } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[Binding]) -> RcDoc { let doc = concat( - serv.iter().map(|(id, func)| { - let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => pp_function(f), - TypeInner::Var(_) => pp_ty(func), + serv.iter().map(|Binding { id, typ }| { + let func_doc = match typ { + IDLType::FuncT(ref f) => pp_function(f), + IDLType::VarT(_) => pp_ty(typ), _ => unreachable!(), }; pp_text(id).append(kwd(" :")).append(func_doc) @@ -202,29 +202,29 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { enclose_space("{", doc, "}") } -fn pp_defs(env: &TypeEnv) -> RcDoc { - lines(env.0.iter().map(|(id, ty)| { +fn pp_defs(env: &IDLMergedProg) -> RcDoc { + lines(env.get_types().iter().map(|(id, typ)| { kwd("type") .append(ident(id)) .append(kwd("=")) - .append(pp_ty(ty)) + .append(pp_ty(typ)) .append(";") })) } -fn pp_actor(ty: &Type) -> RcDoc { - match ty.as_ref() { - TypeInner::Service(ref serv) => pp_service(serv), - TypeInner::Var(_) | TypeInner::Class(_, _) => pp_ty(ty), +fn pp_actor(ty: &IDLType) -> RcDoc { + match ty { + IDLType::ServT(ref serv) => pp_service(serv), + IDLType::VarT(_) | IDLType::ClassT(_, _) => pp_ty(ty), _ => unreachable!(), } } -pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [ArgType]) -> RcDoc<'a> { +pub fn pp_init_args<'a>(env: &'a IDLMergedProg, args: &'a [IDLArgType]) -> RcDoc<'a> { pp_defs(env).append(pp_args(args)) } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { - match actor { +pub fn compile(env: &IDLMergedProg) -> String { + match &env.actor { None => pp_defs(env).pretty(LINE_WIDTH).to_string(), Some(actor) => { let defs = pp_defs(env); diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 4a70cdba0..af0345ac4 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -1,5 +1,6 @@ use super::CandidType; use crate::idl_hash; +use crate::types::syntax::IDLType; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BTreeMap; @@ -177,6 +178,12 @@ impl TypeContainer { } .into() } + + pub fn as_idl_type(&self, ty: &Type) -> IDLType { + // The knot type has already been converted to a var type when adding the type to the env, + // see `self.add` and `self.go` + self.env.as_idl_type(ty) + } } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -325,13 +332,24 @@ impl Type { #[cfg(feature = "printer")] impl fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", crate::pretty::candid::pp_ty(self).pretty(80)) + write!(f, "{}", self.as_ref()) } } #[cfg(feature = "printer")] impl fmt::Display for TypeInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", crate::pretty::candid::pp_ty_inner(self).pretty(80)) + match self { + TypeInner::Future => write!(f, "future"), + TypeInner::Unknown => write!(f, "unknown"), + _ => { + let env = crate::TypeEnv::new(); + write!( + f, + "{}", + crate::pretty::candid::pp_ty(&env.inner_as_idl_type(self)).pretty(80), + ) + } + } } } #[cfg(not(feature = "printer"))] @@ -480,10 +498,11 @@ pub struct Field { #[cfg(feature = "printer")] impl fmt::Display for Field { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let env = crate::TypeEnv::new(); write!( f, "{}", - crate::pretty::candid::pp_field(self, false).pretty(80) + crate::pretty::candid::pp_field(&env.field_to_idl_field(self), false).pretty(80) ) } } @@ -557,7 +576,12 @@ pub struct ArgType { #[cfg(feature = "printer")] impl fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", crate::pretty::candid::pp_function(self).pretty(80)) + let env = crate::TypeEnv::new(); + write!( + f, + "{}", + crate::pretty::candid::pp_function(&env.func_to_idl_func(self)).pretty(80), + ) } } #[cfg(not(feature = "printer"))] diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 1e002317d..930fc6e5e 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -253,8 +253,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( let init_2 = to_tuple(&init2_typ); equal(gamma, env, &init_1, &init_2).context(format!( "Mismatch in init args: {} and {}", - pp_args(init1), - pp_args(init2) + pp_args(env, init1), + pp_args(env, init2) ))?; equal(gamma, env, ty1, ty2) } @@ -316,7 +316,9 @@ fn pp_args(args: &[crate::types::ArgType]) -> String { s } #[cfg(feature = "printer")] -fn pp_args(args: &[crate::types::ArgType]) -> String { +fn pp_args(env: &TypeEnv, args: &[crate::types::ArgType]) -> String { use crate::pretty::candid::pp_args; - pp_args(args).pretty(80).to_string() + pp_args(&env.arg_types_to_idl_arg_types(args)) + .pretty(80) + .to_string() } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 1f943ea2d..98d25e61a 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,4 +1,6 @@ -use crate::types::{FuncMode, Label}; +use std::fmt; + +use crate::types::{ArgType, Field, FuncMode, Function, Label, Type, TypeInner}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum IDLType { @@ -14,6 +16,74 @@ pub enum IDLType { PrincipalT, } +impl IDLType { + pub fn is_tuple(&self) -> bool { + match self { + IDLType::RecordT(fields) => { + for (i, field) in fields.iter().enumerate() { + if field.label.get_id() != (i as u32) { + return false; + } + } + true + } + _ => false, + } + } + + pub fn is_null(&self) -> bool { + matches!(self, IDLType::PrimT(PrimType::Null)) + } +} + +impl fmt::Display for IDLType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", crate::pretty::candid::pp_ty(self).pretty(80)) + } +} + +impl From for Type { + fn from(t: IDLType) -> Self { + match t { + IDLType::PrimT(PrimType::Null) => TypeInner::Null, + IDLType::PrimT(PrimType::Bool) => TypeInner::Bool, + IDLType::PrimT(PrimType::Nat) => TypeInner::Nat, + IDLType::PrimT(PrimType::Int) => TypeInner::Int, + IDLType::PrimT(PrimType::Nat8) => TypeInner::Nat8, + IDLType::PrimT(PrimType::Nat16) => TypeInner::Nat16, + IDLType::PrimT(PrimType::Nat32) => TypeInner::Nat32, + IDLType::PrimT(PrimType::Nat64) => TypeInner::Nat64, + IDLType::PrimT(PrimType::Int8) => TypeInner::Int8, + IDLType::PrimT(PrimType::Int16) => TypeInner::Int16, + IDLType::PrimT(PrimType::Int32) => TypeInner::Int32, + IDLType::PrimT(PrimType::Int64) => TypeInner::Int64, + IDLType::PrimT(PrimType::Float32) => TypeInner::Float32, + IDLType::PrimT(PrimType::Float64) => TypeInner::Float64, + IDLType::PrimT(PrimType::Text) => TypeInner::Text, + IDLType::PrimT(PrimType::Reserved) => TypeInner::Reserved, + IDLType::PrimT(PrimType::Empty) => TypeInner::Empty, + IDLType::VarT(id) => TypeInner::Var(id), + IDLType::FuncT(func) => TypeInner::Func(func.into()), + IDLType::OptT(t) => TypeInner::Opt((*t).into()), + IDLType::VecT(t) => TypeInner::Vec((*t).into()), + IDLType::RecordT(fields) => { + TypeInner::Record(fields.into_iter().map(|t| t.into()).collect()) + } + IDLType::VariantT(fields) => { + TypeInner::Variant(fields.into_iter().map(|t| t.into()).collect()) + } + IDLType::ServT(methods) => { + TypeInner::Service(methods.into_iter().map(|t| (t.id, t.typ.into())).collect()) + } + IDLType::ClassT(args, t) => { + TypeInner::Class(args.into_iter().map(|t| t.into()).collect(), (*t).into()) + } + IDLType::PrincipalT => TypeInner::Principal, + } + .into() + } +} + #[derive(Debug, Clone)] pub struct IDLTypes { pub args: Vec, @@ -66,12 +136,31 @@ pub struct FuncType { pub rets: Vec, } +impl From for Function { + fn from(t: FuncType) -> Self { + Function { + modes: t.modes, + args: t.args.into_iter().map(|t| t.into()).collect(), + rets: t.rets.into_iter().map(|t| t.into()).collect(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct IDLArgType { pub typ: IDLType, pub name: Option, } +impl From for ArgType { + fn from(t: IDLArgType) -> Self { + ArgType { + typ: t.typ.into(), + name: t.name, + } + } +} + impl IDLArgType { pub fn new(typ: IDLType) -> Self { Self { typ, name: None } @@ -96,7 +185,16 @@ pub struct TypeField { pub typ: IDLType, } -#[derive(Debug)] +impl From for Field { + fn from(t: TypeField) -> Self { + Field { + id: t.label.into(), + ty: t.typ.into(), + } + } +} + +#[derive(Debug, Clone)] pub enum Dec { TypD(Binding), ImportType(String), @@ -109,7 +207,7 @@ pub struct Binding { pub typ: IDLType, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct IDLProg { pub decs: Vec, pub actor: Option, @@ -120,3 +218,113 @@ pub struct IDLInitArgs { pub decs: Vec, pub args: Vec, } + +/// Generated from concatenating different `IDLProg`s. +#[derive(Debug, Default)] +pub struct IDLMergedProg { + types: Vec, + pub actor: Option, +} + +impl From for IDLMergedProg { + fn from(other_prog: IDLProg) -> Self { + let mut merged_prog = Self::default(); + merged_prog.add_decs(&other_prog.decs); + merged_prog.set_actor(other_prog.actor); + merged_prog + } +} + +impl From> for IDLMergedProg { + fn from(bindings: Vec) -> Self { + Self { + types: bindings, + actor: None, + } + } +} + +impl IDLMergedProg { + pub fn new() -> Self { + Self::default() + } + + pub fn add_decs(&mut self, decs: &[Dec]) { + let types: Vec = decs + .iter() + .filter_map(|dec| match dec { + Dec::TypD(binding) => Some(binding.clone()), + Dec::ImportType(_) | Dec::ImportServ(_) => None, + }) + .collect(); + self.types.extend(types); + } + + pub fn set_actor(&mut self, other: Option) { + self.actor = other; + } + + pub fn find_type(&self, id: &str) -> Result<&IDLType, String> { + self.types + .iter() + .find_map(|t| if t.id == id { Some(&t.typ) } else { None }) + .ok_or(format!("Type identifier not found: {id}")) + } + + pub fn rec_find_type(&self, name: &str) -> Result<&IDLType, String> { + let t = self.find_type(name)?; + match t { + IDLType::VarT(id) => self.rec_find_type(id), + _ => Ok(t), + } + } + + pub fn get_types(&self) -> Vec<(&str, &IDLType)> { + self.types.iter().map(|t| (t.id.as_str(), &t.typ)).collect() + } + + pub fn get_bindings(&self) -> Vec { + self.types.clone() + } + + pub fn insert_binding(&mut self, binding: Binding) { + self.types.push(binding); + } + + pub fn types_ids(&self) -> Vec<&str> { + self.types.iter().map(|t| t.id.as_str()).collect() + } + + pub fn trace_type(&self, t: &IDLType) -> Result { + match t { + IDLType::VarT(id) => self.trace_type(self.find_type(id)?), + _ => Ok(t.clone()), + } + } + + pub fn service_methods<'a>(&'a self, t: &'a IDLType) -> Result<&'a Vec, String> { + match t { + IDLType::ServT(methods) => Ok(methods), + IDLType::VarT(id) => self.service_methods(self.find_type(id)?), + IDLType::ClassT(_, t) => self.service_methods(t), + _ => Err(format!("not a service type: {t}")), + } + } + + pub fn as_func<'a>(&'a self, t: &'a IDLType) -> Result<&'a FuncType, String> { + match t { + IDLType::FuncT(f) => Ok(f), + IDLType::VarT(id) => self.as_func(self.find_type(id)?), + _ => Err(format!("not a function type: {:?}", t)), + } + } + + pub fn get_method<'a>(&'a self, t: &'a IDLType, id: &'a str) -> Result<&'a FuncType, String> { + for binding in self.service_methods(t)? { + if binding.id == id { + return self.as_func(&binding.typ); + } + } + Err(format!("cannot find method {id}")) + } +} diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 0723f2769..0235e473c 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,6 +1,7 @@ -use crate::types::{Function, Type, TypeInner}; +use crate::types::syntax::{Binding, FuncType, IDLArgType, IDLType, PrimType, TypeField}; +use crate::types::{ArgType, Field, Function, Type, TypeInner}; use crate::{Error, Result}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Debug, Clone, Default)] pub struct TypeEnv(pub BTreeMap); @@ -9,6 +10,7 @@ impl TypeEnv { pub fn new() -> Self { TypeEnv(BTreeMap::new()) } + pub fn merge<'a>(&'a mut self, env: &TypeEnv) -> Result<&'a mut Self> { for (k, v) in &env.0 { let entry = self.0.entry(k.to_string()).or_insert_with(|| v.clone()); @@ -18,6 +20,7 @@ impl TypeEnv { } Ok(self) } + pub fn merge_type(&mut self, env: TypeEnv, ty: Type) -> Type { let tau: BTreeMap = env .0 @@ -35,12 +38,14 @@ impl TypeEnv { } ty.subst(&tau) } + pub fn find_type(&self, name: &str) -> Result<&Type> { match self.0.get(name) { None => Err(Error::msg(format!("Unbound type identifier {name}"))), Some(t) => Ok(t), } } + pub fn rec_find_type(&self, name: &str) -> Result<&Type> { let t = self.find_type(name)?; match t.as_ref() { @@ -48,6 +53,7 @@ impl TypeEnv { _ => Ok(t), } } + pub fn trace_type<'a>(&'a self, t: &'a Type) -> Result { match t.as_ref() { TypeInner::Var(id) => self.trace_type(self.find_type(id)?), @@ -57,6 +63,7 @@ impl TypeEnv { _ => Ok(t.clone()), } } + pub fn as_func<'a>(&'a self, t: &'a Type) -> Result<&'a Function> { match t.as_ref() { TypeInner::Func(f) => Ok(f), @@ -64,6 +71,7 @@ impl TypeEnv { _ => Err(Error::msg(format!("not a function type: {t}"))), } } + pub fn as_service<'a>(&'a self, t: &'a Type) -> Result<&'a [(String, Type)]> { match t.as_ref() { TypeInner::Service(s) => Ok(s), @@ -72,6 +80,7 @@ impl TypeEnv { _ => Err(Error::msg(format!("not a service type: {t}"))), } } + pub fn get_method<'a>(&'a self, t: &'a Type, id: &'a str) -> Result<&'a Function> { for (meth, ty) in self.as_service(t)? { if meth == id { @@ -80,6 +89,7 @@ impl TypeEnv { } Err(Error::msg(format!("cannot find method {id}"))) } + fn is_empty<'a>( &'a self, res: &mut BTreeMap<&'a str, Option>, @@ -115,6 +125,7 @@ impl TypeEnv { Some(Some(b)) => Ok(*b), } } + pub fn replace_empty(&mut self) -> Result<()> { let mut res = BTreeMap::new(); for name in self.0.keys() { @@ -130,7 +141,126 @@ impl TypeEnv { } Ok(()) } + + fn has_cycle<'a>(&'a self, seen: &mut BTreeSet<&'a str>, t: &'a Type) -> Result { + match t.as_ref() { + TypeInner::Var(id) => { + if seen.insert(id) { + let ty = self.find_type(id)?; + self.has_cycle(seen, ty) + } else { + Ok(true) + } + } + _ => Ok(false), + } + } + + pub fn check_cycle(&self) -> Result<()> { + for (id, ty) in self.0.iter() { + let mut seen = BTreeSet::new(); + if self.has_cycle(&mut seen, ty)? { + return Err(Error::msg(format!("{id} has cyclic type definition"))); + } + } + Ok(()) + } + + /// # Panics + /// + /// Panics if the type is [TypeInner::Future] or [TypeInner::Unknown]. + /// You must handle those cases before calling this function. + /// + /// Panics if the id referenced by the [TypeInner::Knot] was not inserted + /// into the env before calling this function. + pub fn inner_as_idl_type(&self, ty: &TypeInner) -> IDLType { + match ty { + TypeInner::Null => IDLType::PrimT(PrimType::Null), + TypeInner::Bool => IDLType::PrimT(PrimType::Bool), + TypeInner::Nat => IDLType::PrimT(PrimType::Nat), + TypeInner::Int => IDLType::PrimT(PrimType::Int), + TypeInner::Nat8 => IDLType::PrimT(PrimType::Nat8), + TypeInner::Nat16 => IDLType::PrimT(PrimType::Nat16), + TypeInner::Nat32 => IDLType::PrimT(PrimType::Nat32), + TypeInner::Nat64 => IDLType::PrimT(PrimType::Nat64), + TypeInner::Int8 => IDLType::PrimT(PrimType::Int8), + TypeInner::Int16 => IDLType::PrimT(PrimType::Int16), + TypeInner::Int32 => IDLType::PrimT(PrimType::Int32), + TypeInner::Int64 => IDLType::PrimT(PrimType::Int64), + TypeInner::Float32 => IDLType::PrimT(PrimType::Float32), + TypeInner::Float64 => IDLType::PrimT(PrimType::Float64), + TypeInner::Text => IDLType::PrimT(PrimType::Text), + TypeInner::Reserved => IDLType::PrimT(PrimType::Reserved), + TypeInner::Empty => IDLType::PrimT(PrimType::Empty), + TypeInner::Var(id) => IDLType::VarT(id.to_string()), + TypeInner::Opt(t) => IDLType::OptT(Box::new(self.as_idl_type(t))), + TypeInner::Vec(t) => IDLType::VecT(Box::new(self.as_idl_type(t))), + TypeInner::Record(fields) => IDLType::RecordT(self.fields_to_idl_fields(fields)), + TypeInner::Variant(fields) => IDLType::VariantT(self.fields_to_idl_fields(fields)), + TypeInner::Func(func) => IDLType::FuncT(self.func_to_idl_func(func)), + TypeInner::Service(methods) => IDLType::ServT(self.methods_to_idl_methods(methods)), + TypeInner::Class(args, t) => IDLType::ClassT( + self.arg_types_to_idl_arg_types(args), + Box::new(self.as_idl_type(t)), + ), + TypeInner::Principal => IDLType::PrincipalT, + TypeInner::Knot(id) => { + let name = id.to_string(); + self.0 + .get(&name) + .unwrap_or_else(|| panic!("Knot type should already be in the env: {:?}", id)); + IDLType::VarT(name) + } + TypeInner::Future | TypeInner::Unknown => unreachable!(), + } + } + + pub fn as_idl_type(&self, ty: &Type) -> IDLType { + self.inner_as_idl_type(ty.as_ref()) + } + + pub fn arg_types_to_idl_arg_types(&self, args: &[ArgType]) -> Vec { + args.iter() + .map(|arg| IDLArgType { + typ: self.as_idl_type(&arg.typ), + name: arg.name.clone(), + }) + .collect() + } + + pub fn func_to_idl_func(&self, func: &Function) -> FuncType { + FuncType { + modes: func.modes.clone(), + args: self.arg_types_to_idl_arg_types(&func.args), + rets: func.rets.iter().map(|arg| self.as_idl_type(arg)).collect(), + } + } + + pub fn field_to_idl_field(&self, field: &Field) -> TypeField { + TypeField { + label: field.id.as_ref().clone(), + typ: self.as_idl_type(&field.ty), + } + } + + fn fields_to_idl_fields(&self, fields: &[Field]) -> Vec { + fields + .iter() + .map(|field| self.field_to_idl_field(field)) + .collect() + } + + fn methods_to_idl_methods(&self, methods: &[(String, Type)]) -> Vec { + methods + .iter() + .map(|(id, t)| Binding { + id: id.clone(), + typ: self.as_idl_type(t), + }) + .collect() + } } + impl std::fmt::Display for TypeEnv { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (k, v) in &self.0 { diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index 9c8fcb965..5abf54374 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -5,9 +5,14 @@ use std::fmt::Debug; use candid::{ candid_method, record, ser::IDLBuilder, - types::value::{IDLValue, IDLValueVisitor}, - types::{get_type, Serializer, Type, TypeInner}, - variant, CandidType, Decode, Deserialize, Encode, Int, + types::{ + get_type, + internal::TypeContainer, + syntax::{IDLType, PrimType, TypeField}, + value::{IDLValue, IDLValueVisitor}, + Label, Serializer, Type, TypeInner, + }, + variant, CandidType, Decode, Deserialize, Encode, Int, TypeEnv, }; use serde::de::DeserializeOwned; @@ -84,29 +89,57 @@ fn any_val() { #[test] fn test_primitive() { - assert_eq!(get_type(&true), TypeInner::Bool.into()); - assert_eq!(get_type(&Box::new(42)), TypeInner::Int32.into()); - assert_eq!(get_type(&Box::new(Int::from(42))), TypeInner::Int.into()); + let env = TypeEnv::new(); + + let bool_prim = get_type(&true); + assert_eq!(bool_prim, TypeInner::Bool.into()); + assert_eq!(env.as_idl_type(&bool_prim), IDLType::PrimT(PrimType::Bool)); + + let null_prim = get_type(&()); + assert_eq!(null_prim, TypeInner::Null.into()); + assert_eq!(env.as_idl_type(&null_prim), IDLType::PrimT(PrimType::Null)); + + let int_prim = get_type(&Box::new(42)); + assert_eq!(int_prim, TypeInner::Int32.into()); + assert_eq!(env.as_idl_type(&int_prim), IDLType::PrimT(PrimType::Int32)); + + let int_prim = get_type(&Box::new(Int::from(42))); + assert_eq!(int_prim, TypeInner::Int.into()); + assert_eq!(env.as_idl_type(&int_prim), IDLType::PrimT(PrimType::Int)); + let opt: Option<&str> = None; + let opt_prim = get_type(&opt); + assert_eq!(opt_prim, TypeInner::Opt(TypeInner::Text.into()).into()); assert_eq!( - get_type(&opt), - TypeInner::Opt(TypeInner::Text.into()).into() - ); - assert_eq!( - get_type(&[0, 1, 2, 3]), - TypeInner::Vec(TypeInner::Int32.into()).into() + env.as_idl_type(&opt_prim), + IDLType::OptT(Box::new(IDLType::PrimT(PrimType::Text))) ); + + let vec_prim = get_type(&[0, 1, 2, 3]); + assert_eq!(vec_prim, TypeInner::Vec(TypeInner::Int32.into()).into()); assert_eq!( - get_type(&std::marker::PhantomData::), - TypeInner::Nat32.into() + env.as_idl_type(&vec_prim), + IDLType::VecT(Box::new(IDLType::PrimT(PrimType::Int32))) ); + + let nat_prim = get_type(&std::marker::PhantomData::); + assert_eq!(nat_prim, TypeInner::Nat32.into()); + assert_eq!(env.as_idl_type(&nat_prim), IDLType::PrimT(PrimType::Nat32)); } #[test] fn test_struct() { + let mut type_container = TypeContainer::new(); + #[derive(Debug, CandidType)] struct Newtype(Int); assert_eq!(Newtype::ty(), TypeInner::Int.into()); + type_container.add::(); + assert_eq!( + type_container.as_idl_type(&Newtype::ty()), + IDLType::PrimT(PrimType::Int) + ); + #[derive(Debug, CandidType)] struct A { foo: Int, @@ -116,6 +149,20 @@ fn test_struct() { A::ty(), record! { foo: TypeInner::Int.into(); bar: TypeInner::Bool.into() } ); + type_container.add::(); + assert_eq!( + type_container.as_idl_type(&A::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("bar".to_string()), + typ: IDLType::PrimT(PrimType::Bool), + }, + TypeField { + label: Label::Named("foo".to_string()), + typ: IDLType::PrimT(PrimType::Int), + }, + ]) + ); #[derive(Debug, CandidType)] struct G { @@ -127,6 +174,20 @@ fn test_struct() { get_type(&res), record! { g1: TypeInner::Int32.into(); g2: TypeInner::Bool.into() } ); + type_container.add::>(); + assert_eq!( + type_container.as_idl_type(&get_type(&res)), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("g1".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("g2".to_string()), + typ: IDLType::PrimT(PrimType::Bool), + }, + ]) + ); #[derive(Debug, CandidType)] struct List { @@ -137,10 +198,71 @@ fn test_struct() { List::ty(), record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::()).into()).into() } ); + type_container.add::(); + assert_eq!( + type_container.as_idl_type(&List::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("head".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("tail".to_string()), + typ: IDLType::OptT(Box::new(IDLType::VarT("List".to_string()))), + }, + ]) + ); + + #[derive(Debug, CandidType)] + struct GenericList { + head: T, + tail: Option>>, + } + assert_eq!( + GenericList::::ty(), + record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::>()).into()).into() } + ); + type_container.add::>(); + assert_eq!( + type_container.as_idl_type(&GenericList::::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("head".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("tail".to_string()), + typ: IDLType::OptT(Box::new(IDLType::VarT("GenericList".to_string()))), + }, + ]) + ); + + #[derive(Debug, CandidType)] + struct Wrap(GenericList); + assert_eq!( + Wrap::ty(), + record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::>()).into()).into() } + ); + type_container.add::(); + assert_eq!( + type_container.as_idl_type(&Wrap::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("head".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("tail".to_string()), + typ: IDLType::OptT(Box::new(IDLType::VarT("GenericList".to_string()))), + }, + ]) + ); } #[test] fn test_variant() { + let mut type_container = TypeContainer::new(); + #[allow(dead_code)] #[derive(Debug, CandidType)] enum E { @@ -160,6 +282,46 @@ fn test_variant() { Newtype: TypeInner::Bool.into(); } ); + type_container.add::(); + assert_eq!( + type_container.as_idl_type(&get_type(&v)), + IDLType::VariantT(vec![ + TypeField { + label: Label::Named("Bar".to_string()), + typ: IDLType::RecordT(vec![ + TypeField { + label: Label::Id(0), + typ: IDLType::PrimT(PrimType::Bool), + }, + TypeField { + label: Label::Id(1), + typ: IDLType::PrimT(PrimType::Int32), + }, + ]), + }, + TypeField { + label: Label::Named("Baz".to_string()), + typ: IDLType::RecordT(vec![ + TypeField { + label: Label::Named("a".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("b".to_string()), + typ: IDLType::PrimT(PrimType::Nat32), + }, + ]), + }, + TypeField { + label: Label::Named("Foo".to_string()), + typ: IDLType::PrimT(PrimType::Null), + }, + TypeField { + label: Label::Named("Newtype".to_string()), + typ: IDLType::PrimT(PrimType::Bool), + }, + ]) + ); } #[derive(CandidType)] diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 7c886563e..60133a391 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -122,6 +122,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { }); let service = quote! { use #candid::types::{CandidType, Function, Type, ArgType, TypeInner}; + use #candid::types::syntax::{Binding, IDLMergedProg}; let mut service = Vec::<(String, Type)>::new(); let mut env = #candid::types::internal::TypeContainer::new(); #(#gen_tys)* @@ -131,16 +132,24 @@ pub(crate) fn export_service(path: Option) -> TokenStream { let actor = if let Some(init) = init { quote! { #init - let actor = Some(TypeInner::Class(init_args, ty).into()); + let actor = TypeInner::Class(init_args, ty).into(); } } else { - quote! { let actor = Some(ty); } + quote! { let actor = ty; } }; let res = quote! { fn __export_service() -> String { #service #actor - let result = #candid::pretty::candid::compile(&env.env, &actor); + + let bindings = env.env.0.iter().map(|(id, t)| Binding { + id: id.clone(), + typ: env.as_idl_type(t), + }).collect::>(); + let mut idl_merged_prog = IDLMergedProg::from(bindings); + idl_merged_prog.set_actor(Some(env.as_idl_type(&actor))); + + let result = #candid::pretty::candid::compile(&idl_merged_prog); format!("{}", result) } }; diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index bf80a0524..b8c84038c 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -1,5 +1,8 @@ use crate::{Error, Result}; -use candid::types::{Type, TypeEnv, TypeInner}; +use candid::types::{ + syntax::{IDLMergedProg, IDLType}, + Type, TypeEnv, TypeInner, +}; use std::collections::{BTreeMap, BTreeSet}; /// Select a subset of methods from an actor. @@ -8,9 +11,7 @@ pub fn project_methods( actor: &Option, mut methods: Vec, ) -> Result { - let actor = actor - .as_ref() - .ok_or_else(|| Error::Custom(anyhow::anyhow!("no actor")))?; + let actor = actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; let service = env.as_service(actor)?; let filtered = service .iter() @@ -25,10 +26,7 @@ pub fn project_methods( .cloned() .collect(); if !methods.is_empty() { - return Err(Error::Custom(anyhow::anyhow!( - "methods not found: {:?}", - methods - ))); + return Err(Error::msg(format!("methods not found: {:?}", methods))); } Ok(TypeInner::Service(filtered).into()) } @@ -37,40 +35,40 @@ pub fn project_methods( pub fn chase_type<'a>( seen: &mut BTreeSet<&'a str>, res: &mut Vec<&'a str>, - env: &'a TypeEnv, - t: &'a Type, + prog: &'a IDLMergedProg, + t: &'a IDLType, ) -> Result<()> { - use TypeInner::*; - match t.as_ref() { - Var(id) => { + use IDLType::*; + match t { + VarT(id) => { if seen.insert(id) { - let t = env.find_type(id)?; - chase_type(seen, res, env, t)?; + let t = prog.find_type(id).map_err(Error::msg)?; + chase_type(seen, res, prog, t)?; res.push(id); } } - Opt(ty) | Vec(ty) => chase_type(seen, res, env, ty)?, - Record(fs) | Variant(fs) => { + OptT(ty) | VecT(ty) => chase_type(seen, res, prog, ty)?, + RecordT(fs) | VariantT(fs) => { for f in fs.iter() { - chase_type(seen, res, env, &f.ty)?; + chase_type(seen, res, prog, &f.typ)?; } } - Func(f) => { + FuncT(f) => { let args = f.args.iter().map(|arg| &arg.typ); for ty in args.clone().chain(f.rets.iter()) { - chase_type(seen, res, env, ty)?; + chase_type(seen, res, prog, ty)?; } } - Service(ms) => { - for (_, ty) in ms.iter() { - chase_type(seen, res, env, ty)?; + ServT(bindings) => { + for binding in bindings.iter() { + chase_type(seen, res, prog, &binding.typ)?; } } - Class(args, t) => { + ClassT(args, t) => { for arg in args.iter() { - chase_type(seen, res, env, &arg.typ)?; + chase_type(seen, res, prog, &arg.typ)?; } - chase_type(seen, res, env, t)?; + chase_type(seen, res, prog, t)?; } _ => (), } @@ -79,23 +77,21 @@ pub fn chase_type<'a>( /// Gather type definitions mentioned in actor, return the non-recursive type names in topological order. /// Recursive types can appear in any order. -pub fn chase_actor<'a>(env: &'a TypeEnv, actor: &'a Type) -> Result> { +pub fn chase_actor<'a>(prog: &'a IDLMergedProg, actor: &'a IDLType) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); - chase_type(&mut seen, &mut res, env, actor)?; + chase_type(&mut seen, &mut res, prog, 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>> { +pub fn chase_def_use(prog: &IDLMergedProg) -> Result>> { let mut res = BTreeMap::new(); - let actor = env.trace_type(actor)?; - if let TypeInner::Class(args, _) = actor.as_ref() { + let actor = prog.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; + let actor = prog.trace_type(actor).map_err(Error::msg)?; + if let IDLType::ClassT(args, _) = &actor { for (i, arg) in args.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; + chase_type(&mut BTreeSet::new(), &mut used, prog, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -103,74 +99,77 @@ pub fn chase_def_use<'a>( } } } - for (id, ty) in env.as_service(&actor)? { - let func = env.as_func(ty)?; + for binding in prog.service_methods(&actor).map_err(Error::msg)? { + let func = prog.as_func(&binding.typ).map_err(Error::msg)?; for (i, arg) in func.args.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; + chase_type(&mut BTreeSet::new(), &mut used, prog, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) - .push(format!("{}.arg{}", id, i)); + .push(format!("{}.arg{}", binding.id, i)); } } for (i, arg) in func.rets.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, prog, arg)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) - .push(format!("{}.ret{}", id, i)); + .push(format!("{}.ret{}", binding.id, i)); } } } Ok(res) } -pub fn chase_types<'a>(env: &'a TypeEnv, tys: &'a [Type]) -> Result> { +pub fn chase_types<'a>(prog: &'a IDLMergedProg, tys: &'a [IDLType]) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); for t in tys.iter() { - chase_type(&mut seen, &mut res, env, t)?; + chase_type(&mut seen, &mut res, prog, t)?; } Ok(res) } /// Given a `def_list` produced by the `chase_actor` function, infer which types are recursive -pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result> { +pub fn infer_rec<'a>( + _prog: &'a IDLMergedProg, + def_list: &'a [&'a str], +) -> Result> { let mut seen = BTreeSet::new(); let mut res = BTreeSet::new(); fn go<'a>( seen: &mut BTreeSet<&'a str>, res: &mut BTreeSet<&'a str>, - _env: &'a TypeEnv, - t: &'a Type, + _env: &'a IDLMergedProg, + t: &'a IDLType, ) -> Result<()> { - use TypeInner::*; - match t.as_ref() { - Var(id) => { + use IDLType::*; + match t { + VarT(id) => { if seen.insert(id) { res.insert(id); } } - Opt(ty) | Vec(ty) => go(seen, res, _env, ty)?, - Record(fs) | Variant(fs) => { + OptT(ty) | VecT(ty) => go(seen, res, _env, ty)?, + RecordT(fs) | VariantT(fs) => { for f in fs.iter() { - go(seen, res, _env, &f.ty)?; + go(seen, res, _env, &f.typ)?; } } - Func(f) => { + FuncT(f) => { let args = f.args.iter().map(|arg| &arg.typ); for ty in args.clone().chain(f.rets.iter()) { go(seen, res, _env, ty)?; } } - Service(ms) => { - for (_, ty) in ms.iter() { - go(seen, res, _env, ty)?; + ServT(ms) => { + for binding in ms.iter() { + go(seen, res, _env, &binding.typ)?; } } - Class(args, t) => { + ClassT(args, t) => { for arg in args.iter() { go(seen, res, _env, &arg.typ)?; } @@ -181,8 +180,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result bool { - match t.as_ref() { - TypeInner::Record(ref fs) => { +pub(super) fn is_tuple(t: &IDLType) -> bool { + match t { + IDLType::RecordT(ref fs) => { if fs.is_empty() { return false; } - for (i, field) in fs.iter().enumerate() { - if field.id.get_id() != (i as u32) { - return false; - } - } - true + t.is_tuple() } _ => false, } @@ -96,9 +94,9 @@ pub(crate) fn ident(id: &str) -> RcDoc { } } -fn pp_ty(ty: &Type) -> RcDoc { - use TypeInner::*; - match ty.as_ref() { +fn pp_prim_ty(ty: &PrimType) -> RcDoc { + use PrimType::*; + match ty { Null => str("IDL.Null"), Bool => str("IDL.Bool"), Nat => str("IDL.Nat"), @@ -116,28 +114,34 @@ fn pp_ty(ty: &Type) -> RcDoc { Text => str("IDL.Text"), Reserved => str("IDL.Reserved"), Empty => str("IDL.Empty"), - Var(ref s) => ident(s), - Principal => str("IDL.Principal"), - Opt(ref t) => str("IDL.Opt").append(enclose("(", pp_ty(t), ")")), - Vec(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), - Record(ref fs) => { + } +} + +fn pp_ty(ty: &IDLType) -> RcDoc { + use IDLType::*; + match ty { + PrimT(ty) => pp_prim_ty(ty), + VarT(ref s) => ident(s), + PrincipalT => str("IDL.Principal"), + OptT(ref t) => str("IDL.Opt").append(enclose("(", pp_ty(t), ")")), + VecT(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), + RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ","); str("IDL.Tuple").append(enclose("(", tuple, ")")) } else { str("IDL.Record").append(pp_fields(fs)) } } - Variant(ref fs) => str("IDL.Variant").append(pp_fields(fs)), - Func(ref func) => str("IDL.Func").append(pp_function(func)), - Service(ref serv) => str("IDL.Service").append(pp_service(serv)), - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), + VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), + FuncT(ref func) => str("IDL.Func").append(pp_function(func)), + ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), + ClassT(_, _) => unreachable!(), } } -fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") .append(RcDoc::as_string(n)) @@ -146,18 +150,18 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field(field: &Field) -> RcDoc { - pp_label(&field.id) +fn pp_field(field: &TypeField) -> RcDoc { + pp_label(&field.label) .append(kwd(":")) - .append(pp_ty(&field.ty)) + .append(pp_ty(&field.typ)) } -fn pp_fields(fs: &[Field]) -> RcDoc { +fn pp_fields(fs: &[TypeField]) -> RcDoc { let fields = concat(fs.iter().map(pp_field), ","); enclose_space("({", fields, "})") } -fn pp_function(func: &Function) -> RcDoc { +fn pp_function(func: &FuncType) -> RcDoc { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -166,12 +170,12 @@ fn pp_function(func: &Function) -> RcDoc { enclose("(", doc, ")").nest(INDENT_SPACE) } -fn pp_args(args: &[ArgType]) -> RcDoc { +fn pp_args(args: &[IDLArgType]) -> RcDoc { let doc = concat(args.iter().map(|arg| pp_ty(&arg.typ)), ","); enclose("[", doc, "]") } -fn pp_rets(args: &[Type]) -> RcDoc { +fn pp_rets(args: &[IDLType]) -> RcDoc { let doc = concat(args.iter().map(pp_ty), ","); enclose("[", doc, "]") } @@ -186,17 +190,17 @@ fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc { enclose("[", doc, "]") } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[Binding]) -> RcDoc { let doc = concat( serv.iter() - .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))), + .map(|Binding { id, typ }| quote_ident(id).append(kwd(":")).append(pp_ty(typ))), ",", ); enclose_space("({", doc, "})") } fn pp_defs<'a>( - env: &'a TypeEnv, + env: &'a IDLMergedProg, def_list: &'a [&'a str], recs: &'a BTreeSet<&'a str>, ) -> RcDoc<'a> { @@ -221,47 +225,47 @@ fn pp_defs<'a>( recs_doc.append(defs) } -fn pp_actor<'a>(ty: &'a Type, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { - match ty.as_ref() { - TypeInner::Service(_) => pp_ty(ty), - TypeInner::Var(id) => { +fn pp_actor<'a>(ty: &'a IDLType, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { + match ty { + IDLType::ServT(_) => pp_ty(ty), + IDLType::VarT(id) => { if recs.contains(&*id.clone()) { str(id).append(".getType()") } else { str(id) } } - TypeInner::Class(_, t) => pp_actor(t, recs), + IDLType::ClassT(_, t) => pp_actor(t, recs), _ => unreachable!(), } } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { - match actor { +pub fn compile(prog: &IDLMergedProg) -> String { + match &prog.actor { None => { - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); - let recs = infer_rec(env, &def_list).unwrap(); - let doc = pp_defs(env, &def_list, &recs); + let def_list: Vec<_> = prog.types_ids(); + let recs = infer_rec(prog, &def_list).unwrap(); + let doc = pp_defs(prog, &def_list, &recs); doc.pretty(LINE_WIDTH).to_string() } Some(actor) => { - let def_list = chase_actor(env, actor).unwrap(); - let recs = infer_rec(env, &def_list).unwrap(); - let defs = pp_defs(env, &def_list, &recs); - let types = if let TypeInner::Class(ref args, _) = actor.as_ref() { + let def_list = chase_actor(prog, actor).unwrap(); + let recs = infer_rec(prog, &def_list).unwrap(); + let defs = pp_defs(prog, &def_list, &recs); + let init = if let IDLType::ClassT(ref args, _) = actor { args.iter().map(|arg| arg.typ.clone()).collect::>() } else { Vec::new() }; - let init = types.as_slice(); + let init = init.as_slice(); let actor = kwd("return").append(pp_actor(actor, &recs)).append(";"); let body = defs.append(actor); let doc = str("export const idlFactory = ({ IDL }) => ") .append(enclose_space("{", body, "};")); // export init args - let init_defs = chase_types(env, init).unwrap(); - let init_recs = infer_rec(env, &init_defs).unwrap(); - let init_defs_doc = pp_defs(env, &init_defs, &init_recs); + let init_defs = chase_types(prog, init).unwrap(); + let init_recs = infer_rec(prog, &init_defs).unwrap(); + let init_defs_doc = pp_defs(prog, &init_defs, &init_recs); let init_doc = kwd("return").append(pp_rets(init)).append(";"); let init_doc = init_defs_doc.append(init_doc); let init_doc = diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 29dac5dba..a41dc387e 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -3,23 +3,20 @@ use candid::pretty::candid::is_valid_as_id; use candid::pretty::utils::*; -use candid::types::{ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeInner}; -use candid::TypeEnv; +use candid::types::syntax::IDLMergedProg; +use candid::types::{ + syntax::{Binding, FuncType, IDLArgType, IDLType, PrimType, TypeField}, + FuncMode, Label, +}; use pretty::RcDoc; -// The definition of tuple is language specific. -fn is_tuple(t: &Type) -> bool { - match t.as_ref() { - TypeInner::Record(ref fs) => { +fn is_tuple(t: &IDLType) -> bool { + match t { + IDLType::RecordT(ref fs) => { if fs.len() <= 1 { return false; } - for (i, field) in fs.iter().enumerate() { - if field.id.get_id() != (i as u32) { - return false; - } - } - true + t.is_tuple() } _ => false, } @@ -92,9 +89,9 @@ fn escape(id: &str, is_method: bool) -> RcDoc { } } -fn pp_ty(ty: &Type) -> RcDoc { - use TypeInner::*; - match ty.as_ref() { +fn pp_prim_ty(prim: &PrimType) -> RcDoc { + use PrimType::*; + match prim { Null => str("Null"), Bool => str("Bool"), Nat => str("Nat"), @@ -112,21 +109,28 @@ fn pp_ty(ty: &Type) -> RcDoc { Text => str("Text"), Reserved => str("Any"), Empty => str("None"), - Var(ref s) => escape(s, false), - Principal => str("Principal"), - Opt(ref t) => str("?").append(pp_ty(t)), - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("Blob"), - Vec(ref t) => enclose("[", pp_ty(t), "]"), - Record(ref fs) => { + } +} + +fn pp_ty(ty: &IDLType) -> RcDoc { + use IDLType::*; + match ty { + PrimT(prim) => pp_prim_ty(prim), + VarT(ref s) => escape(s, false), + PrincipalT => str("Principal"), + OptT(ref t) => str("?").append(pp_ty(t)), + VecT(ref t) if matches!(t.as_ref(), PrimT(PrimType::Nat8)) => str("Blob"), + VecT(ref t) => enclose("[", pp_ty(t), "]"), + RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ","); enclose("(", tuple, ")") } else { let fields = concat(fs.iter().map(pp_field), ";"); enclose_space("{", fields, "}") } } - Variant(ref fs) => { + VariantT(ref fs) => { if fs.is_empty() { str("{#}") } else { @@ -134,22 +138,21 @@ fn pp_ty(ty: &Type) -> RcDoc { enclose_space("{", fields, "}") } } - Func(ref func) => pp_function(func), - Service(ref serv) => pp_service(serv), - Class(ref args, ref t) => { + FuncT(ref func) => pp_function(func), + ServT(ref serv) => pp_service(serv), + ClassT(ref args, ref t) => { let doc = pp_args(args).append(" -> async "); match t.as_ref() { - Service(ref serv) => doc.append(pp_service(serv)), - Var(ref s) => doc.append(s), + IDLType::ServT(ref serv) => doc.append(pp_service(serv)), + IDLType::VarT(ref s) => doc.append(s), _ => unreachable!(), } } - Knot(_) | Unknown | Future => unreachable!(), } } -fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(str) => escape(str, false), Label::Id(n) | Label::Unnamed(n) => str("_") .append(RcDoc::as_string(n)) @@ -158,19 +161,21 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field(field: &Field) -> RcDoc { - pp_label(&field.id).append(" : ").append(pp_ty(&field.ty)) +fn pp_field(field: &TypeField) -> RcDoc { + pp_label(&field.label) + .append(" : ") + .append(pp_ty(&field.typ)) } -fn pp_variant(field: &Field) -> RcDoc { - let doc = str("#").append(pp_label(&field.id)); - if *field.ty != TypeInner::Null { - doc.append(" : ").append(pp_ty(&field.ty)) +fn pp_variant(field: &TypeField) -> RcDoc { + let doc = str("#").append(pp_label(&field.label)); + if !field.typ.is_null() { + doc.append(" : ").append(pp_ty(&field.typ)) } else { doc } } -fn pp_function(func: &Function) -> RcDoc { +fn pp_function(func: &FuncType) -> RcDoc { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); match func.modes.as_slice() { @@ -194,7 +199,7 @@ fn pp_function(func: &Function) -> RcDoc { } .nest(INDENT_SPACE) } -fn pp_args(args: &[ArgType]) -> RcDoc { +fn pp_args(args: &[IDLArgType]) -> RcDoc { match args { [ty] => { let typ = if is_tuple(&ty.typ) { @@ -222,7 +227,7 @@ fn pp_args(args: &[ArgType]) -> RcDoc { } } -fn pp_rets(args: &[Type]) -> RcDoc { +fn pp_rets(args: &[IDLType]) -> RcDoc { match args { [ty] => { if is_tuple(ty) { @@ -238,41 +243,42 @@ fn pp_rets(args: &[Type]) -> RcDoc { } } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[Binding]) -> RcDoc { let doc = concat( serv.iter() - .map(|(id, func)| escape(id, true).append(" : ").append(pp_ty(func))), + .map(|b| escape(&b.id, true).append(" : ").append(pp_ty(&b.typ))), ";", ); kwd("actor").append(enclose_space("{", doc, "}")) } -fn pp_defs(env: &TypeEnv) -> RcDoc { - lines(env.0.iter().map(|(id, ty)| { +fn pp_defs<'a>(bindings: &[(&'a str, &'a IDLType)]) -> RcDoc<'a> { + lines(bindings.iter().map(|(id, typ)| { kwd("public type") .append(escape(id, false)) .append(" = ") - .append(pp_ty(ty)) + .append(pp_ty(typ)) .append(";") })) } -fn pp_actor(ty: &Type) -> RcDoc { - match ty.as_ref() { - TypeInner::Service(ref serv) => pp_service(serv), - TypeInner::Var(_) | TypeInner::Class(_, _) => pp_ty(ty), +fn pp_actor(ty: &IDLType) -> RcDoc { + match ty { + IDLType::ServT(ref serv) => pp_service(serv), + IDLType::VarT(_) | IDLType::ClassT(_, _) => pp_ty(ty), _ => unreachable!(), } } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { +pub fn compile(prog: &IDLMergedProg) -> String { let header = r#"// This is a generated Motoko binding. // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. "#; - let doc = match actor { - None => pp_defs(env), + let bindings = prog.get_types(); + let doc = match &prog.actor { + None => pp_defs(&bindings), Some(actor) => { - let defs = pp_defs(env); + let defs = pp_defs(&bindings); let actor = kwd("public type Self =").append(pp_actor(actor)); defs.append(actor) } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 4fa80e7e1..eb12def6e 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -3,8 +3,11 @@ use crate::{ configs::{ConfigState, ConfigTree, Configs, Context, StateElem}, Deserialize, }; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; -use candid::{pretty::utils::*, types::ArgType}; +use candid::types::{ + syntax::{Binding, FuncType, IDLArgType, IDLType, PrimType, TypeField}, + Label, +}; +use candid::{pretty::utils::*, types::syntax::IDLMergedProg}; use convert_case::{Case, Casing}; use pretty::RcDoc; use serde::Serialize; @@ -82,26 +85,35 @@ struct State<'a> { type RecPoints<'a> = BTreeSet<&'a str>; // The definition of tuple is language specific. -pub(crate) fn is_tuple(fs: &[Field]) -> bool { - if fs.is_empty() { - return false; +pub(crate) fn is_tuple(t: &IDLType) -> bool { + match t { + IDLType::RecordT(ref fs) => { + if fs.is_empty() { + return false; + } + t.is_tuple() + } + _ => false, } - !fs.iter() - .enumerate() - .any(|(i, field)| field.id.get_id() != (i as u32)) } -fn as_result(fs: &[Field]) -> Option<(&Type, &Type, bool)> { +fn as_result(fs: &[TypeField]) -> Option<(&IDLType, &IDLType, bool)> { match fs { - [Field { id: ok, ty: t_ok }, Field { id: err, ty: t_err }] - if **ok == Label::Named("Ok".to_string()) - && **err == Label::Named("Err".to_string()) => - { + [TypeField { + label: ok, + typ: t_ok, + }, TypeField { + label: err, + typ: t_err, + }] if *ok == Label::Named("Ok".to_string()) && *err == Label::Named("Err".to_string()) => { Some((t_ok, t_err, false)) } - [Field { id: ok, ty: t_ok }, Field { id: err, ty: t_err }] - if **ok == Label::Named("ok".to_string()) - && **err == Label::Named("err".to_string()) => - { + [TypeField { + label: ok, + typ: t_ok, + }, TypeField { + label: err, + typ: t_err, + }] if *ok == Label::Named("ok".to_string()) && *err == Label::Named("err".to_string()) => { Some((t_ok, t_err, true)) } _ => None, @@ -154,23 +166,22 @@ fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { } impl<'a> State<'a> { - fn generate_test(&mut self, src: &Type, use_type: &str) { + fn generate_test(&mut self, src: &IDLType, use_type: &str) { if self.tests.contains_key(use_type) { return; } - let def_list = chase_actor(self.state.env, src).unwrap(); - let env = TypeEnv( - self.state - .env - .0 - .iter() - .filter(|(k, _)| def_list.contains(&k.as_str())) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - ); + let def_list = chase_actor(self.state.prog, src).unwrap(); + let filtered_bindings = self + .state + .prog + .get_bindings() + .into_iter() + .filter(|b| def_list.contains(&b.id.as_str())) + .collect::>(); + let prog = IDLMergedProg::from(filtered_bindings); let src = candid::pretty::candid::pp_init_args( - &env, - &[ArgType { + &prog, + &[IDLArgType { name: None, typ: src.clone(), }], @@ -189,8 +200,8 @@ fn test_{test_name}() {{ ); self.tests.insert(use_type.to_string(), body); } - fn pp_ty<'b>(&mut self, ty: &'b Type, is_ref: bool) -> RcDoc<'b> { - use TypeInner::*; + fn pp_ty<'b>(&mut self, ty: &'b IDLType, is_ref: bool) -> RcDoc<'b> { + use IDLType::*; let elem = StateElem::Type(ty); let old = self.state.push_state(&elem); let res = if let Some(t) = &self.state.config.use_type { @@ -202,25 +213,25 @@ fn test_{test_name}() {{ self.state.update_stats("use_type"); res } else { - match ty.as_ref() { - Null => str("()"), - Bool => str("bool"), - Nat => str("candid::Nat"), - Int => str("candid::Int"), - Nat8 => str("u8"), - Nat16 => str("u16"), - Nat32 => str("u32"), - Nat64 => str("u64"), - Int8 => str("i8"), - Int16 => str("i16"), - Int32 => str("i32"), - Int64 => str("i64"), - Float32 => str("f32"), - Float64 => str("f64"), - Text => str("String"), - Reserved => str("candid::Reserved"), - Empty => str("candid::Empty"), - Var(ref id) => { + match ty { + PrimT(PrimType::Null) => str("()"), + PrimT(PrimType::Bool) => str("bool"), + PrimT(PrimType::Nat) => str("candid::Nat"), + PrimT(PrimType::Int) => str("candid::Int"), + PrimT(PrimType::Nat8) => str("u8"), + PrimT(PrimType::Nat16) => str("u16"), + PrimT(PrimType::Nat32) => str("u32"), + PrimT(PrimType::Nat64) => str("u64"), + PrimT(PrimType::Int8) => str("i8"), + PrimT(PrimType::Int16) => str("i16"), + PrimT(PrimType::Int32) => str("i32"), + PrimT(PrimType::Int64) => str("i64"), + PrimT(PrimType::Float32) => str("f32"), + PrimT(PrimType::Float64) => str("f64"), + PrimT(PrimType::Text) => str("String"), + PrimT(PrimType::Reserved) => str("candid::Reserved"), + PrimT(PrimType::Empty) => str("candid::Empty"), + VarT(ref id) => { let name = if let Some(name) = &self.state.config.name { let res = RcDoc::text(name.clone()); self.state.update_stats("name"); @@ -234,13 +245,15 @@ fn test_{test_name}() {{ name } } - Principal => str("Principal"), - Opt(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), + PrincipalT => str("Principal"), + OptT(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), - Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), - Record(ref fs) => self.pp_record_fields(fs, false, is_ref), - Variant(ref fs) => { + VecT(ref t) if matches!(t.as_ref(), PrimT(PrimType::Nat8)) => { + str("serde_bytes::ByteBuf") + } + VecT(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), + RecordT(ref fs) => self.pp_record_fields(fs, false, is_ref), + VariantT(ref fs) => { // only possible for result variant let (ok, err, is_motoko) = as_result(fs).unwrap(); // This is a hacky way to redirect Result type @@ -268,16 +281,15 @@ fn test_{test_name}() {{ let body = ok.append(", ").append(err); RcDoc::text(result).append(enclose("<", body, ">")) } - Func(_) => unreachable!(), // not possible after rewriting - Service(_) => unreachable!(), // not possible after rewriting - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), + FuncT(_) // not possible after rewriting + | ServT(_) // not possible after rewriting + | ClassT(_, _) => unreachable!(), } }; self.state.pop_state(old, elem); res } - fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, need_vis: bool) -> RcDoc<'b> { + fn pp_label<'b>(&mut self, id: &'b Label, is_variant: bool, need_vis: bool) -> RcDoc<'b> { let vis = if need_vis { self.state.update_stats("visibility"); pp_vis(&self.state.config.visibility) @@ -292,7 +304,7 @@ fn test_{test_name}() {{ .map(|s| RcDoc::text(s).append(RcDoc::line())) .unwrap_or(RcDoc::nil()); self.state.update_stats("attributes"); - match &**id { + match id { Label::Named(id) => { let (doc, is_rename) = if let Some(name) = &self.state.config.name { let res = (RcDoc::text(name.clone()), true); @@ -324,7 +336,7 @@ fn test_{test_name}() {{ } } } - fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { + fn pp_tuple<'b>(&mut self, fs: &'b [TypeField], need_vis: bool, is_ref: bool) -> RcDoc<'b> { let tuple = fs.iter().enumerate().map(|(i, f)| { let lab = i.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); @@ -334,30 +346,40 @@ fn test_{test_name}() {{ } else { RcDoc::nil() }; - let res = vis.append(self.pp_ty(&f.ty, is_ref)).append(","); + let res = vis.append(self.pp_ty(&f.typ, is_ref)).append(","); self.state.pop_state(old, StateElem::Label(&lab)); res }); enclose("(", RcDoc::concat(tuple), ")") } - fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool, is_ref: bool) -> RcDoc<'b> { - let lab = field.id.to_string(); + fn pp_record_field<'b>( + &mut self, + field: &'b TypeField, + need_vis: bool, + is_ref: bool, + ) -> RcDoc<'b> { + let lab = field.label.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); let res = self - .pp_label(&field.id, false, need_vis) + .pp_label(&field.label, false, need_vis) .append(kwd(":")) - .append(self.pp_ty(&field.ty, is_ref)); + .append(self.pp_ty(&field.typ, is_ref)); self.state.pop_state(old, StateElem::Label(&lab)); res } - fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { + fn pp_record_fields<'b>( + &mut self, + fs: &'b [TypeField], + need_vis: bool, + is_ref: bool, + ) -> RcDoc<'b> { 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) { + let res = if is_tuple(&IDLType::RecordT(fs.to_vec())) { self.pp_tuple(fs, need_vis, is_ref) } else { let fields: Vec<_> = fs @@ -372,24 +394,24 @@ fn test_{test_name}() {{ } res } - fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { - let lab = field.id.to_string(); + fn pp_variant_field<'b>(&mut self, field: &'b TypeField) -> RcDoc<'b> { + let lab = field.label.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = match field.ty.as_ref() { - TypeInner::Null => self.pp_label(&field.id, true, false), - TypeInner::Record(fs) => self - .pp_label(&field.id, true, false) + let res = match &field.typ { + IDLType::PrimT(PrimType::Null) => self.pp_label(&field.label, true, false), + IDLType::RecordT(fs) => self + .pp_label(&field.label, true, false) .append(self.pp_record_fields(fs, false, false)), - _ => self.pp_label(&field.id, true, false).append(enclose( + _ => self.pp_label(&field.label, true, false).append(enclose( "(", - self.pp_ty(&field.ty, false), + self.pp_ty(&field.typ, false), ")", )), }; self.state.pop_state(old, StateElem::Label(&lab)); res } - fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { + fn pp_variant_fields<'b>(&mut self, fs: &'b [TypeField]) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::TypeStr("variant")); let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); let fields = concat(fields.into_iter(), ","); @@ -405,7 +427,7 @@ fn test_{test_name}() {{ self.state.pop_state(old, StateElem::Label(id)); continue; } - let ty = self.state.env.find_type(id).unwrap(); + let ty = self.state.prog.find_type(id).unwrap(); let name = self .state .config @@ -424,9 +446,9 @@ fn test_{test_name}() {{ .clone() .map(RcDoc::text) .unwrap_or(RcDoc::text("#[derive(CandidType, Deserialize)]")); - let line = match ty.as_ref() { - TypeInner::Record(fs) => { - let separator = if is_tuple(fs) { + let line = match ty { + IDLType::RecordT(fs) => { + let separator = if is_tuple(ty) { RcDoc::text(";") } else { RcDoc::nil() @@ -440,7 +462,7 @@ fn test_{test_name}() {{ .append(self.pp_record_fields(fs, true, false)) .append(separator) } - TypeInner::Variant(fs) => { + IDLType::VariantT(fs) => { if as_result(fs).is_some() { vis.append(kwd("type")) .append(name) @@ -457,13 +479,13 @@ fn test_{test_name}() {{ .append(self.pp_variant_fields(fs)) } } - TypeInner::Func(func) => str("candid::define_function!(") + IDLType::FuncT(func) => str("candid::define_function!(") .append(vis) .append(name) .append(" : ") .append(self.pp_ty_func(func)) .append(");"), - TypeInner::Service(serv) => str("candid::define_service!(") + IDLType::ServT(serv) => str("candid::define_service!(") .append(vis) .append(name) .append(" : ") @@ -493,7 +515,7 @@ fn test_{test_name}() {{ } lines(res.into_iter()) } - fn pp_args<'b>(&mut self, args: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { + fn pp_args<'b>(&mut self, args: &'b [IDLArgType], prefix: &'b str) -> RcDoc<'b> { let doc: Vec<_> = args .iter() .enumerate() @@ -508,7 +530,7 @@ fn test_{test_name}() {{ let doc = concat(doc.into_iter(), ","); enclose("(", doc, ")") } - fn pp_rets<'b>(&mut self, args: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + fn pp_rets<'b>(&mut self, args: &'b [IDLType], prefix: &'b str) -> RcDoc<'b> { let doc: Vec<_> = args .iter() .enumerate() @@ -523,7 +545,7 @@ fn test_{test_name}() {{ let doc = concat(doc.into_iter(), ","); enclose("(", doc, ")") } - fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { + fn pp_ty_func<'b>(&mut self, f: &'b FuncType) -> RcDoc<'b> { let lab = StateElem::TypeStr("func"); let old = self.state.push_state(&lab); let args = self.pp_args(&f.args, "arg"); @@ -537,19 +559,19 @@ fn test_{test_name}() {{ self.state.pop_state(old, lab); res } - fn pp_ty_service<'b>(&mut self, serv: &'b [(String, Type)]) -> RcDoc<'b> { + fn pp_ty_service<'b>(&mut self, serv: &'b [Binding]) -> RcDoc<'b> { let lab = StateElem::TypeStr("service"); let old = self.state.push_state(&lab); let mut list = Vec::new(); - for (id, func) in serv.iter() { - let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => enclose("candid::func!(", self.pp_ty_func(f), ")"), - TypeInner::Var(_) => self.pp_ty(func, true).append("::ty()"), + for binding in serv.iter() { + let func_doc = match &binding.typ { + IDLType::FuncT(ref f) => enclose("candid::func!(", self.pp_ty_func(f), ")"), + IDLType::VarT(_) => self.pp_ty(&binding.typ, true).append("::ty()"), _ => unreachable!(), }; list.push( RcDoc::text("\"") - .append(id) + .append(&binding.id) .append(kwd("\" :")) .append(func_doc), ); @@ -559,7 +581,7 @@ fn test_{test_name}() {{ self.state.pop_state(old, lab); res } - fn pp_function(&mut self, id: &str, func: &Function) -> Method { + fn pp_function(&mut self, id: &str, func: &FuncType) -> Method { use candid::types::internal::FuncMode; let old = self.state.push_state(&StateElem::Label(id)); let name = self @@ -623,9 +645,9 @@ fn test_{test_name}() {{ self.state.pop_state(old, StateElem::Label(id)); res } - fn pp_actor(&mut self, actor: &Type) -> (Vec, Option>) { - let actor = self.state.env.trace_type(actor).unwrap(); - let init = if let TypeInner::Class(args, _) = actor.as_ref() { + fn pp_actor(&mut self, actor: &IDLType) -> (Vec, Option>) { + let actor = self.state.prog.trace_type(actor).unwrap(); + let init = if let IDLType::ClassT(args, _) = &actor { let old = self.state.push_state(&StateElem::Label("init")); let args: Vec<_> = args .iter() @@ -650,11 +672,11 @@ fn test_{test_name}() {{ } else { None }; - let serv = self.state.env.as_service(&actor).unwrap(); + let serv = self.state.prog.service_methods(&actor).unwrap(); let mut res = Vec::new(); - for (id, func) in serv.iter() { - let func = self.state.env.as_func(func).unwrap(); - res.push(self.pp_function(id, func)); + for binding in serv.iter() { + let func = self.state.prog.as_func(&binding.typ).unwrap(); + res.push(self.pp_function(&binding.id, func)); } (res, init) } @@ -674,16 +696,16 @@ pub struct Method { pub rets: Vec, pub mode: String, } -pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Output, Vec) { +pub fn emit_bindgen(tree: &Config, prog: &IDLMergedProg) -> (Output, Vec) { let mut state = NominalState { - state: crate::configs::State::new(&tree.0, env), + state: crate::configs::State::new(&tree.0, prog), }; - let (env, actor) = state.nominalize_all(actor); + let env = state.nominalize_all(); let old_stats = state.state.stats.clone(); - let def_list = if let Some(actor) = &actor { + let def_list = if let Some(actor) = &env.actor { chase_actor(&env, actor).unwrap() } else { - env.0.iter().map(|pair| pair.0.as_ref()).collect::>() + env.types_ids() }; let recs = infer_rec(&env, &def_list).unwrap(); let mut state = State { @@ -693,7 +715,7 @@ pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Outp }; state.state.stats = old_stats; let defs = state.pp_defs(&def_list); - let (methods, init_args) = if let Some(actor) = &actor { + let (methods, init_args) = if let Some(actor) = &env.actor { state.pp_actor(actor) } else { (Vec::new(), None) @@ -754,15 +776,14 @@ impl Default for ExternalConfig { } pub fn compile( tree: &Config, - env: &TypeEnv, - actor: &Option, + prog: &IDLMergedProg, mut external: ExternalConfig, ) -> (String, Vec) { 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") => { - let metadata = crate::utils::get_metadata(env, actor); + let metadata = crate::utils::get_metadata(prog); if let Some(metadata) = metadata { external.0.insert("metadata".to_string(), metadata); } @@ -777,7 +798,7 @@ pub fn compile( } _ => unimplemented!(), }; - let (output, unused) = emit_bindgen(tree, env, actor); + let (output, unused) = emit_bindgen(tree, prog); (output_handlebar(output, external, &source), unused) } @@ -812,47 +833,55 @@ struct NominalState<'a> { } impl NominalState<'_> { // Convert structural typing to nominal typing to fit Rust's type system - fn nominalize(&mut self, env: &mut TypeEnv, path: &mut Vec, t: &Type) -> Type { + fn nominalize( + &mut self, + env: &mut IDLMergedProg, + path: &mut Vec, + t: &IDLType, + ) -> IDLType { let elem = StateElem::Type(t); - let old = if matches!(t.as_ref(), TypeInner::Func(_)) { + let old = if matches!(t, IDLType::FuncT(_)) { // strictly speaking, we want to avoid func label from the main service. But this is probably good enough. None } else { Some(self.state.push_state(&elem)) }; - let res = match t.as_ref() { - TypeInner::Opt(ty) => { + let res = match t { + IDLType::OptT(ty) => { path.push(TypePath::Opt); let ty = self.nominalize(env, path, ty); path.pop(); - TypeInner::Opt(ty) + IDLType::OptT(Box::new(ty)) } - TypeInner::Vec(ty) => { + IDLType::VecT(ty) => { path.push(TypePath::Vec); let ty = self.nominalize(env, path, ty); path.pop(); - TypeInner::Vec(ty) + IDLType::VecT(Box::new(ty)) } - TypeInner::Record(fs) => { + IDLType::RecordT(fs) => { if matches!( path.last(), None | Some(TypePath::VariantField(_)) | Some(TypePath::Id(_)) - ) || is_tuple(fs) + ) || is_tuple(t) { let fs: Vec<_> = fs .iter() - .map(|Field { id, ty }| { - let lab = id.to_string(); + .map(|TypeField { label, typ }| { + let lab = label.to_string(); let elem = StateElem::Label(&lab); let old = self.state.push_state(&elem); - path.push(TypePath::RecordField(id.to_string())); - let ty = self.nominalize(env, path, ty); + path.push(TypePath::RecordField(lab.clone())); + let ty = self.nominalize(env, path, typ); path.pop(); self.state.pop_state(old, elem); - Field { id: id.clone(), ty } + TypeField { + label: label.clone(), + typ: ty, + } }) .collect(); - TypeInner::Record(fs) + IDLType::RecordT(fs) } else { let new_var = if let Some(name) = &self.state.config.name { let res = name.to_string(); @@ -864,33 +893,39 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Record(fs.to_vec()).into(), + &IDLType::RecordT(fs.to_vec()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } } - TypeInner::Variant(fs) => { + IDLType::VariantT(fs) => { let is_result = as_result(fs).is_some(); if matches!(path.last(), None | Some(TypePath::Id(_))) || is_result { let fs: Vec<_> = fs .iter() - .map(|Field { id, ty }| { - let lab = id.to_string(); + .map(|TypeField { label, typ }| { + let lab = label.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); if is_result { // so that inner record gets a new name - path.push(TypePath::ResultField(id.to_string())); + path.push(TypePath::ResultField(lab.clone())); } else { - path.push(TypePath::VariantField(id.to_string())); + path.push(TypePath::VariantField(lab.clone())); } - let ty = self.nominalize(env, path, ty); + let ty = self.nominalize(env, path, typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - Field { id: id.clone(), ty } + TypeField { + label: label.clone(), + typ: ty, + } }) .collect(); - TypeInner::Variant(fs) + IDLType::VariantT(fs) } else { let new_var = if let Some(name) = &self.state.config.name { let res = name.to_string(); @@ -902,16 +937,19 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Variant(fs.to_vec()).into(), + &IDLType::VariantT(fs.to_vec()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } } - TypeInner::Func(func) => match path.last() { + IDLType::FuncT(func) => match path.last() { None | Some(TypePath::Id(_)) => { let func = func.clone(); - TypeInner::Func(Function { + IDLType::FuncT(FuncType { modes: func.modes, args: func .args @@ -929,7 +967,7 @@ impl NominalState<'_> { let ty = self.nominalize(env, path, &arg.typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - ArgType { + IDLArgType { name: arg.name.clone(), typ: ty, } @@ -967,23 +1005,26 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Func(func.clone()).into(), + &IDLType::FuncT(func.clone()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } }, - TypeInner::Service(serv) => match path.last() { - None | Some(TypePath::Id(_)) => TypeInner::Service( + IDLType::ServT(serv) => match path.last() { + None | Some(TypePath::Id(_)) => IDLType::ServT( serv.iter() - .map(|(meth, ty)| { - let lab = meth.to_string(); + .map(|Binding { id, typ }| { + let lab = id.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); - path.push(TypePath::Id(meth.to_string())); - let ty = self.nominalize(env, path, ty); + path.push(TypePath::Id(lab.clone())); + let ty = self.nominalize(env, path, typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - (meth.clone(), ty) + Binding { id: lab, typ: ty } }) .collect(), ), @@ -998,13 +1039,16 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Service(serv.clone()).into(), + &IDLType::ServT(serv.clone()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } }, - TypeInner::Class(args, ty) => TypeInner::Class( + IDLType::ClassT(args, ty) => IDLType::ClassT( args.iter() .map(|arg| { let elem = StateElem::Label("init"); @@ -1013,36 +1057,42 @@ impl NominalState<'_> { let ty = self.nominalize(env, path, &arg.typ); path.pop(); self.state.pop_state(old, elem); - ArgType { + IDLArgType { name: arg.name.clone(), typ: ty, } }) .collect(), - self.nominalize(env, path, ty), + Box::new(self.nominalize(env, path, ty)), ), t => t.clone(), - } - .into(); + }; if let Some(old) = old { self.state.pop_state(old, elem); } res } - fn nominalize_all(&mut self, actor: &Option) -> (TypeEnv, Option) { - let mut res = TypeEnv(Default::default()); - for (id, ty) in self.state.env.0.iter() { + fn nominalize_all(&mut self) -> IDLMergedProg { + let mut res = IDLMergedProg::new(); + for (id, typ) in self.state.prog.get_types() { let elem = StateElem::Label(id); let old = self.state.push_state(&elem); - let ty = self.nominalize(&mut res, &mut vec![TypePath::Id(id.clone())], ty); - res.0.insert(id.to_string(), ty); + let ty = self.nominalize(&mut res, &mut vec![TypePath::Id(id.to_string())], typ); + res.insert_binding(Binding { + id: id.to_string(), + typ: ty, + }); self.state.pop_state(old, elem); } - let actor = actor + let actor = self + .state + .prog + .actor .as_ref() .map(|ty| self.nominalize(&mut res, &mut vec![], ty)); - (res, actor) + res.set_actor(actor); + res } } fn get_hbs() -> handlebars::Handlebars<'static> { diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 020ba3adf..a8b825345 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -1,11 +1,14 @@ use super::javascript::{ident, is_tuple}; use candid::pretty::utils::*; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::types::{ + syntax::{Binding, FuncType, IDLMergedProg, IDLType, PrimType, TypeField}, + Label, +}; use pretty::RcDoc; -fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { - use TypeInner::*; - match ty.as_ref() { +fn pp_prim_ty(ty: &PrimType) -> RcDoc { + use PrimType::*; + match ty { Null => str("null"), Bool => str("boolean"), Nat => str("bigint"), @@ -23,25 +26,39 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { Text => str("string"), Reserved => str("any"), Empty => str("never"), - Var(ref id) => { - let ty = env.rec_find_type(id).unwrap(); - if matches!(ty.as_ref(), Func(_)) { + } +} + +fn pp_ty<'a>(prog: &'a IDLMergedProg, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { + use IDLType::*; + match ty { + PrimT(ref ty) => pp_prim_ty(ty), + VarT(ref id) => { + let ty = prog.rec_find_type(id).unwrap(); + if matches!(ty, FuncT(_)) { return pp_inline_func(); } - if is_ref && matches!(ty.as_ref(), Service(_)) { + if is_ref && matches!(ty, ServT(_)) { return pp_inline_service(); } ident(id) } - Principal => str("Principal"), - Opt(ref t) => str("[] | ").append(enclose("[", pp_ty(env, t, is_ref), "]")), - Vec(ref t) => { + PrincipalT => str("Principal"), + OptT(ref t) => str("[] | ").append(enclose("[", pp_ty(prog, t, is_ref), "]")), + VecT(ref t) => { let ty = match t.as_ref() { - Var(ref id) => { - let ty = env.rec_find_type(id).unwrap(); + VarT(ref id) => { + let ty = prog.rec_find_type(id).unwrap(); if matches!( - ty.as_ref(), - Nat8 | Nat16 | Nat32 | Nat64 | Int8 | Int16 | Int32 | Int64 + ty, + PrimT(PrimType::Nat8) + | PrimT(PrimType::Nat16) + | PrimT(PrimType::Nat32) + | PrimT(PrimType::Nat64) + | PrimT(PrimType::Int8) + | PrimT(PrimType::Int16) + | PrimT(PrimType::Int32) + | PrimT(PrimType::Int64) ) { ty } else { @@ -50,43 +67,42 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { } _ => t, }; - match ty.as_ref() { - Nat8 => str("Uint8Array | number[]"), - Nat16 => str("Uint16Array | number[]"), - Nat32 => str("Uint32Array | number[]"), - Nat64 => str("BigUint64Array | bigint[]"), - Int8 => str("Int8Array | number[]"), - Int16 => str("Int16Array | number[]"), - Int32 => str("Int32Array | number[]"), - Int64 => str("BigInt64Array | bigint[]"), - _ => str("Array").append(enclose("<", pp_ty(env, t, is_ref), ">")), + match ty { + PrimT(PrimType::Nat8) => str("Uint8Array | number[]"), + PrimT(PrimType::Nat16) => str("Uint16Array | number[]"), + PrimT(PrimType::Nat32) => str("Uint32Array | number[]"), + PrimT(PrimType::Nat64) => str("BigUint64Array | bigint[]"), + PrimT(PrimType::Int8) => str("Int8Array | number[]"), + PrimT(PrimType::Int16) => str("Int16Array | number[]"), + PrimT(PrimType::Int32) => str("Int32Array | number[]"), + PrimT(PrimType::Int64) => str("BigInt64Array | bigint[]"), + _ => str("Array").append(enclose("<", pp_ty(prog, t, is_ref), ">")), } } - Record(ref fs) => { + RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(env, &f.ty, is_ref)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(prog, &f.typ, is_ref)), ","); enclose("[", tuple, "]") } else { - let fields = concat(fs.iter().map(|f| pp_field(env, f, is_ref)), ","); + let fields = concat(fs.iter().map(|f| pp_field(prog, f, is_ref)), ","); enclose_space("{", fields, "}") } } - Variant(ref fs) => { + VariantT(ref fs) => { if fs.is_empty() { str("never") } else { strict_concat( fs.iter() - .map(|f| enclose_space("{", pp_field(env, f, is_ref), "}")), + .map(|f| enclose_space("{", pp_field(prog, f, is_ref), "}")), " |", ) .nest(INDENT_SPACE) } } - Func(_) => pp_inline_func(), - Service(_) => pp_inline_service(), - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), + FuncT(_) => pp_inline_func(), + ServT(_) => pp_inline_service(), + ClassT(_, _) => unreachable!(), } } @@ -98,8 +114,8 @@ fn pp_inline_service<'a>() -> RcDoc<'a> { str("Principal") } -fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") .append(RcDoc::as_string(n)) @@ -108,21 +124,21 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field<'a>(env: &'a TypeEnv, field: &'a Field, is_ref: bool) -> RcDoc<'a> { - pp_label(&field.id) +fn pp_field<'a>(prog: &'a IDLMergedProg, field: &'a TypeField, is_ref: bool) -> RcDoc<'a> { + pp_label(&field.label) .append(kwd(":")) - .append(pp_ty(env, &field.ty, is_ref)) + .append(pp_ty(prog, &field.typ, is_ref)) } -fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { - let args = func.args.iter().map(|arg| pp_ty(env, &arg.typ, true)); +fn pp_function<'a>(prog: &'a IDLMergedProg, func: &'a FuncType) -> RcDoc<'a> { + let args = func.args.iter().map(|arg| pp_ty(prog, &arg.typ, true)); let args = enclose("[", concat(args, ","), "]"); let rets = match func.rets.len() { 0 => str("undefined"), - 1 => pp_ty(env, &func.rets[0], true), + 1 => pp_ty(prog, &func.rets[0], true), _ => enclose( "[", - concat(func.rets.iter().map(|ty| pp_ty(env, ty, true)), ","), + concat(func.rets.iter().map(|ty| pp_ty(prog, ty, true)), ","), "]", ), }; @@ -133,12 +149,12 @@ fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { ) } -fn pp_service<'a>(env: &'a TypeEnv, serv: &'a [(String, Type)]) -> RcDoc<'a> { +fn pp_service<'a>(prog: &'a IDLMergedProg, serv: &'a [Binding]) -> RcDoc<'a> { let doc = concat( - serv.iter().map(|(id, func)| { - let func = match func.as_ref() { - TypeInner::Func(ref func) => pp_function(env, func), - TypeInner::Var(ref id) => ident(id), + serv.iter().map(|Binding { id, typ }| { + let func = match typ { + IDLType::FuncT(ref func) => pp_function(prog, func), + IDLType::VarT(ref id) => ident(id), _ => unreachable!(), }; quote_ident(id).append(kwd(":")).append(func) @@ -148,24 +164,24 @@ fn pp_service<'a>(env: &'a TypeEnv, serv: &'a [(String, Type)]) -> RcDoc<'a> { enclose_space("{", doc, "}") } -fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { +fn pp_defs<'a>(prog: &'a IDLMergedProg, def_list: &'a [&'a str]) -> RcDoc<'a> { lines(def_list.iter().map(|id| { - let ty = env.find_type(id).unwrap(); - let export = match ty.as_ref() { - TypeInner::Record(_) if !ty.is_tuple() => kwd("export interface") + let ty = prog.find_type(id).unwrap(); + let export = match ty { + IDLType::RecordT(_) if !ty.is_tuple() => kwd("export interface") .append(ident(id)) .append(" ") - .append(pp_ty(env, ty, false)), - TypeInner::Service(ref serv) => kwd("export interface") + .append(pp_ty(prog, ty, false)), + IDLType::ServT(ref serv) => kwd("export interface") .append(ident(id)) .append(" ") - .append(pp_service(env, serv)), - TypeInner::Func(ref func) => kwd("export type") + .append(pp_service(prog, serv)), + IDLType::FuncT(ref func) => kwd("export type") .append(ident(id)) .append(" = ") - .append(pp_function(env, func)) + .append(pp_function(prog, func)) .append(";"), - TypeInner::Var(ref inner_id) => kwd("export type") + IDLType::VarT(ref inner_id) => kwd("export type") .append(ident(id)) .append(" = ") .append(ident(inner_id)) @@ -173,36 +189,34 @@ fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { _ => kwd("export type") .append(ident(id)) .append(" = ") - .append(pp_ty(env, ty, false)) + .append(pp_ty(prog, ty, false)) .append(";"), }; export })) } -fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type) -> RcDoc<'a> { - match ty.as_ref() { - TypeInner::Service(ref serv) => { - kwd("export interface _SERVICE").append(pp_service(env, serv)) - } - TypeInner::Var(id) => kwd("export interface _SERVICE extends") +fn pp_actor<'a>(prog: &'a IDLMergedProg, ty: &'a IDLType) -> RcDoc<'a> { + match ty { + IDLType::ServT(ref serv) => kwd("export interface _SERVICE").append(pp_service(prog, serv)), + IDLType::VarT(id) => kwd("export interface _SERVICE extends") .append(str(id)) .append(str(" {}")), - TypeInner::Class(_, t) => pp_actor(env, t), + IDLType::ClassT(_, t) => pp_actor(prog, t), _ => unreachable!(), } } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { +pub fn compile(prog: &IDLMergedProg) -> String { let header = r#"import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; "#; - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); - let defs = pp_defs(env, &def_list); - let actor = match actor { + let def_list = prog.types_ids(); + let defs = pp_defs(prog, &def_list); + let actor = match &prog.actor { None => RcDoc::nil(), - Some(actor) => pp_actor(env, actor) + Some(actor) => pp_actor(prog, actor) .append(RcDoc::line()) .append("export declare const idlFactory: IDL.InterfaceFactory;") .append(RcDoc::line()) diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 4b6a57cde..0378c2ea2 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use candid::types::{Type, TypeEnv, TypeInner}; +use candid::types::syntax::{IDLMergedProg, IDLType, PrimType}; use serde::de::DeserializeOwned; use std::collections::{BTreeMap, BTreeSet}; use toml::{Table, Value}; @@ -12,7 +12,7 @@ pub struct State<'a, T: ConfigState> { pub stats: BTreeMap, u32>, pub config: T, pub config_source: BTreeMap>, - pub env: &'a TypeEnv, + pub prog: &'a IDLMergedProg, } pub struct ConfigBackup { config: T, @@ -20,7 +20,7 @@ pub struct ConfigBackup { } #[derive(Debug)] pub enum StateElem<'a> { - Type(&'a Type), + Type(&'a IDLType), TypeStr(&'a str), Label(&'a str), } @@ -36,7 +36,7 @@ pub enum ScopePos { } impl<'a, T: ConfigState> State<'a, T> { - pub fn new(tree: &'a ConfigTree, env: &'a TypeEnv) -> Self { + pub fn new(tree: &'a ConfigTree, prog: &'a IDLMergedProg) -> Self { let mut config = T::default(); let mut config_source = BTreeMap::new(); if let Some(state) = &tree.state { @@ -53,7 +53,7 @@ impl<'a, T: ConfigState> State<'a, T> { stats: BTreeMap::new(), config, config_source, - env, + prog, } } /// Match paths in the scope first. If `scope` is None, clear the scope. @@ -374,38 +374,40 @@ fn generate_state_tree(v: Value) -> Result> { Err(anyhow::anyhow!("Expected a table")) } } -fn path_name(t: &Type) -> String { - match t.as_ref() { - TypeInner::Null => "null", - TypeInner::Bool => "bool", - TypeInner::Nat => "nat", - TypeInner::Int => "int", - TypeInner::Nat8 => "nat8", - TypeInner::Nat16 => "nat16", - TypeInner::Nat32 => "nat32", - TypeInner::Nat64 => "nat64", - TypeInner::Int8 => "int8", - TypeInner::Int16 => "int16", - TypeInner::Int32 => "int32", - TypeInner::Int64 => "int64", - TypeInner::Float32 => "float32", - TypeInner::Float64 => "float64", - TypeInner::Text => "text", - TypeInner::Reserved => "reserved", - TypeInner::Empty => "empty", - TypeInner::Var(id) => id, - TypeInner::Knot(id) => id.name, - TypeInner::Principal => "principal", - TypeInner::Opt(_) => "opt", - TypeInner::Vec(t) if matches!(t.as_ref(), TypeInner::Nat8) => "blob", - TypeInner::Vec(_) => "vec", - TypeInner::Record(_) => "record", - TypeInner::Variant(_) => "variant", - TypeInner::Func(_) => "func", - TypeInner::Service(_) => "service", - TypeInner::Future => "future", - TypeInner::Class(..) => "func:init", - TypeInner::Unknown => unreachable!(), +fn prim_path_name(t: &PrimType) -> &str { + match t { + PrimType::Null => "null", + PrimType::Bool => "bool", + PrimType::Nat => "nat", + PrimType::Int => "int", + PrimType::Nat8 => "nat8", + PrimType::Nat16 => "nat16", + PrimType::Nat32 => "nat32", + PrimType::Nat64 => "nat64", + PrimType::Int8 => "int8", + PrimType::Int16 => "int16", + PrimType::Int32 => "int32", + PrimType::Int64 => "int64", + PrimType::Float32 => "float32", + PrimType::Float64 => "float64", + PrimType::Text => "text", + PrimType::Reserved => "reserved", + PrimType::Empty => "empty", + } +} +fn path_name(t: &IDLType) -> String { + match t { + IDLType::PrimT(t) => prim_path_name(t), + IDLType::VarT(id) => id, + IDLType::PrincipalT => "principal", + IDLType::OptT(_) => "opt", + IDLType::VecT(t) if matches!(t.as_ref(), IDLType::PrimT(PrimType::Nat8)) => "blob", + IDLType::VecT(_) => "vec", + IDLType::RecordT(_) => "record", + IDLType::VariantT(_) => "variant", + IDLType::FuncT(_) => "func", + IDLType::ServT(_) => "service", + IDLType::ClassT(..) => "func:init", } .to_string() } @@ -520,8 +522,8 @@ Vec = { width = 2, size = 10 } t.clone(), ); assert_eq!(tree.max_depth, 4); - let env = TypeEnv::default(); - let mut state = State::new(&tree, &env); + let prog = IDLMergedProg::new(); + let mut state = State::new(&tree, &prog); state.with_scope( &Some(Scope { method: "f", diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 4086ac3c8..ba0596dd7 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -65,7 +65,7 @@ //! // Or alternatively, use check_prog to check in-memory did file //! // Note that file import is ignored by check_prog. //! let mut env = TypeEnv::new(); -//! let actor: Type = check_prog(&mut env, &ast)?.unwrap(); +//! let actor = check_prog(&mut env, &ast)?.unwrap(); //! //! let method = env.get_method(&actor, "g").unwrap(); //! assert_eq!(method.is_query(), true); diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 8a38e586d..1c3f9e517 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -1,9 +1,9 @@ use super::configs::{ConfigState, Configs, Context, Scope, State, StateElem}; use crate::{Error, Result}; use arbitrary::{unstructured::Int, Arbitrary, Unstructured}; +use candid::types::syntax::{IDLMergedProg, IDLType, PrimType, TypeField}; use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; -use candid::types::{Field, Type, TypeEnv, TypeInner}; -use candid::Deserialize; +use candid::{Deserialize, TypeEnv}; use std::collections::HashSet; use std::convert::TryFrom; @@ -63,7 +63,7 @@ impl ConfigState for GenConfig { } fn update_state(&mut self, elem: &StateElem) { if let StateElem::Type(t) = elem { - if !matches!(t.as_ref(), TypeInner::Var(_)) { + if !matches!(t, IDLType::VarT(_)) { self.depth = self.depth.map(|d| d - 1); self.size = self.size.map(|s| s - 1); } @@ -71,7 +71,7 @@ impl ConfigState for GenConfig { } fn restore_state(&mut self, elem: &StateElem) { if let StateElem::Type(t) = elem { - if !matches!(t.as_ref(), TypeInner::Var(_)) { + if !matches!(t, IDLType::VarT(_)) { self.depth = self.depth.map(|d| d + 1); } } @@ -112,67 +112,69 @@ impl ConfigState for GenConfig { pub struct RandState<'a>(State<'a, GenConfig>); impl RandState<'_> { - pub fn any(&mut self, u: &mut Unstructured, ty: &Type) -> Result { + pub fn any(&mut self, u: &mut Unstructured, ty: &IDLType) -> Result { let old_config = self.0.push_state(&StateElem::Type(ty)); if let Some(vec) = &self.0.config.value { let v = u.choose(vec)?; let v: IDLValue = super::parse_idl_value(v)?; - let v = v.annotate_type(true, self.0.env, ty)?; + let env = TypeEnv::new(); + let t = crate::typing::ast_to_type(&env, ty).unwrap(); + let v = v.annotate_type(true, &env, &t)?; self.0.pop_state(old_config, StateElem::Type(ty)); self.0.update_stats("value"); return Ok(v); } - let res = Ok(match ty.as_ref() { - TypeInner::Var(id) => { - let ty = self.0.env.rec_find_type(id)?; + let res = Ok(match ty { + IDLType::VarT(id) => { + let ty = self.0.prog.rec_find_type(id).map_err(Error::msg)?; self.any(u, ty)? } - TypeInner::Null => IDLValue::Null, - TypeInner::Reserved => IDLValue::Reserved, - TypeInner::Bool => IDLValue::Bool(u.arbitrary()?), - TypeInner::Int => { + IDLType::PrimT(PrimType::Null) => IDLValue::Null, + IDLType::PrimT(PrimType::Reserved) => IDLValue::Reserved, + IDLType::PrimT(PrimType::Bool) => IDLValue::Bool(u.arbitrary()?), + IDLType::PrimT(PrimType::Int) => { self.0.update_stats("range"); IDLValue::Int(arbitrary_num::(u, self.0.config.range)?.into()) } - TypeInner::Nat => { + IDLType::PrimT(PrimType::Nat) => { self.0.update_stats("range"); IDLValue::Nat(arbitrary_num::(u, self.0.config.range)?.into()) } - TypeInner::Nat8 => { + IDLType::PrimT(PrimType::Nat8) => { self.0.update_stats("range"); IDLValue::Nat8(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Nat16 => { + IDLType::PrimT(PrimType::Nat16) => { self.0.update_stats("range"); IDLValue::Nat16(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Nat32 => { + IDLType::PrimT(PrimType::Nat32) => { self.0.update_stats("range"); IDLValue::Nat32(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Nat64 => { + IDLType::PrimT(PrimType::Nat64) => { self.0.update_stats("range"); IDLValue::Nat64(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int8 => { + IDLType::PrimT(PrimType::Int8) => { self.0.update_stats("range"); IDLValue::Int8(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int16 => { + IDLType::PrimT(PrimType::Int16) => { self.0.update_stats("range"); IDLValue::Int16(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int32 => { + IDLType::PrimT(PrimType::Int32) => { self.0.update_stats("range"); IDLValue::Int32(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int64 => { + IDLType::PrimT(PrimType::Int64) => { self.0.update_stats("range"); IDLValue::Int64(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Float32 => IDLValue::Float32(u.arbitrary()?), - TypeInner::Float64 => IDLValue::Float64(u.arbitrary()?), - TypeInner::Text => { + IDLType::PrimT(PrimType::Float32) => IDLValue::Float32(u.arbitrary()?), + IDLType::PrimT(PrimType::Float64) => IDLValue::Float64(u.arbitrary()?), + IDLType::PrimT(PrimType::Text) => { self.0.update_stats("text"); self.0.update_stats("width"); IDLValue::Text(arbitrary_text( @@ -181,7 +183,7 @@ impl RandState<'_> { &self.0.config.width, )?) } - TypeInner::Opt(t) => { + IDLType::OptT(t) => { self.0.update_stats("depth"); self.0.update_stats("size"); let depths = if self.0.config.depth.is_some_and(|d| d <= 0) @@ -189,7 +191,7 @@ impl RandState<'_> { { [1, 0] } else { - [1, size(self.0.env, t).unwrap_or(MAX_DEPTH)] + [1, size(self.0.prog, t).unwrap_or(MAX_DEPTH)] }; let idx = arbitrary_variant(u, &depths)?; if idx == 0 { @@ -198,10 +200,10 @@ impl RandState<'_> { IDLValue::Opt(Box::new(self.any(u, t)?)) } } - TypeInner::Vec(t) => { + IDLType::VecT(t) => { self.0.update_stats("width"); let width = self.0.config.width.or_else(|| { - let elem_size = size(self.0.env, t).unwrap_or(MAX_DEPTH); + let elem_size = size(self.0.prog, t).unwrap_or(MAX_DEPTH); self.0.update_stats("size"); Some(std::cmp::max(0, self.0.config.size.unwrap_or(0)) as usize / elem_size) }); @@ -213,25 +215,25 @@ impl RandState<'_> { } IDLValue::Vec(vec) } - TypeInner::Record(fs) => { + IDLType::RecordT(fs) => { let mut res = Vec::new(); - for Field { id, ty } in fs.iter() { - let lab_str = id.to_string(); + for TypeField { label, typ } in fs.iter() { + let lab_str = label.to_string(); let elem = StateElem::Label(&lab_str); let old_config = self.0.push_state(&elem); - let val = self.any(u, ty)?; + let val = self.any(u, typ)?; self.0.pop_state(old_config, elem); res.push(IDLField { - id: id.as_ref().clone(), + id: label.clone(), val, }); } IDLValue::Record(res) } - TypeInner::Variant(fs) => { + IDLType::VariantT(fs) => { let choices = fs .iter() - .map(|Field { ty, .. }| size(self.0.env, ty).unwrap_or(MAX_DEPTH)); + .map(|TypeField { typ, .. }| size(self.0.prog, typ).unwrap_or(MAX_DEPTH)); self.0.update_stats("size"); self.0.update_stats("depth"); let sizes: Vec<_> = if self.0.config.depth.is_some_and(|d| d <= 0) @@ -243,20 +245,20 @@ impl RandState<'_> { choices.collect() }; let idx = arbitrary_variant(u, &sizes)?; - let Field { id, ty } = &fs[idx]; - let lab_str = id.to_string(); + let TypeField { label, typ } = &fs[idx]; + let lab_str = label.to_string(); let elem = StateElem::Label(&lab_str); let old_config = self.0.push_state(&elem); - let val = self.any(u, ty)?; + let val = self.any(u, typ)?; self.0.pop_state(old_config, elem); let field = IDLField { - id: id.as_ref().clone(), + id: label.clone(), val, }; IDLValue::Variant(VariantValue(Box::new(field), idx as u64)) } - TypeInner::Principal => IDLValue::Principal(crate::Principal::arbitrary(u)?), - TypeInner::Func(_) => { + IDLType::PrincipalT => IDLValue::Principal(crate::Principal::arbitrary(u)?), + IDLType::FuncT(_) => { self.0.update_stats("text"); self.0.update_stats("width"); IDLValue::Func( @@ -264,7 +266,7 @@ impl RandState<'_> { arbitrary_text(u, &self.0.config.text, &self.0.config.width)?, ) } - TypeInner::Service(_) => IDLValue::Service(crate::Principal::arbitrary(u)?), + IDLType::ServT(_) => IDLValue::Service(crate::Principal::arbitrary(u)?), _ => unimplemented!(), }); self.0.pop_state(old_config, StateElem::Type(ty)); @@ -275,15 +277,15 @@ impl RandState<'_> { pub fn any( seed: &[u8], configs: Configs, - env: &TypeEnv, - types: &[Type], + prog: &IDLMergedProg, + types: &[IDLType], scope: &Option, ) -> Result { let mut u = arbitrary::Unstructured::new(seed); let tree = super::configs::ConfigTree::from_configs("random", configs)?; let mut args = Vec::new(); for (i, t) in types.iter().enumerate() { - let mut state = State::new(&tree, env); + let mut state = State::new(&tree, prog); state.with_scope(scope, i); let mut state = RandState(state); state.0.push_state(&StateElem::Label(&i.to_string())); @@ -293,33 +295,33 @@ pub fn any( Ok(IDLArgs { args }) } -fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option { - use TypeInner::*; - Some(match t.as_ref() { - Var(id) => { +fn size_helper(prog: &IDLMergedProg, seen: &mut HashSet, t: &IDLType) -> Option { + use IDLType::*; + Some(match t { + VarT(id) => { if seen.insert(id.to_string()) { - let ty = env.rec_find_type(id).unwrap(); - let res = size_helper(env, seen, ty)?; + let ty = prog.rec_find_type(id).unwrap(); + let res = size_helper(prog, seen, ty)?; seen.remove(id); res } else { return None; } } - Empty => 0, - Opt(t) => 1 + size_helper(env, seen, t)?, - Vec(t) => 1 + size_helper(env, seen, t)? * 2, - Record(fs) => { + PrimT(PrimType::Empty) => 0, + OptT(t) => 1 + size_helper(prog, seen, t)?, + VecT(t) => 1 + size_helper(prog, seen, t)? * 2, + RecordT(fs) => { let mut sum = 0; - for Field { ty, .. } in fs.iter() { - sum += size_helper(env, seen, ty)?; + for TypeField { typ, .. } in fs.iter() { + sum += size_helper(prog, seen, typ)?; } 1 + sum } - Variant(fs) => { + VariantT(fs) => { let mut max = 0; - for Field { ty, .. } in fs.iter() { - let s = size_helper(env, seen, ty)?; + for TypeField { typ, .. } in fs.iter() { + let s = size_helper(prog, seen, typ)?; if s > max { max = s; }; @@ -330,9 +332,9 @@ fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option Option { +fn size(prog: &IDLMergedProg, t: &IDLType) -> Option { let mut seen = HashSet::new(); - size_helper(env, &mut seen, t) + size_helper(prog, &mut seen, t) } fn choose_range(u: &mut Unstructured, ranges: &[std::ops::RangeInclusive]) -> Result { diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index 9f3dbd970..974c4768e 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -27,19 +27,6 @@ pub enum Input { Blob(Vec), } -/// Generate assertions for host value -pub struct HostTest { - pub desc: String, - pub asserts: Vec, -} -pub enum HostAssert { - // The encoded bytes is not unique - Encode(IDLArgs, Vec, bool, Vec), - NotEncode(IDLArgs, Vec), - Decode(Vec, Vec, bool, IDLArgs), - NotDecode(Vec, Vec), -} - impl Assert { pub fn desc(&self) -> String { match &self.desc { @@ -82,70 +69,6 @@ impl std::str::FromStr for Test { } } -impl HostTest { - pub fn from_assert(assert: &Assert, env: &TypeEnv, types: &[Type]) -> Self { - use HostAssert::*; - let types = types.to_vec(); - let mut asserts = Vec::new(); - match &assert.left { - Input::Text(s) => { - // Without type annotation, numbers are all of type int. - // Assertion may not pass. - let parsed = crate::parse_idl_args(s); - if parsed.is_err() { - let desc = format!("(skip) {}", assert.desc()); - return HostTest { desc, asserts }; - } - let parsed = parsed.unwrap(); - if !assert.pass && assert.right.is_none() { - asserts.push(NotEncode(parsed, types)); - } else { - let bytes = parsed.to_bytes_with_types(env, &types).unwrap(); - asserts.push(Encode(parsed.clone(), types.clone(), true, bytes.clone())); - // round tripping - let vals = parsed.annotate_types(true, env, &types).unwrap(); - asserts.push(Decode(bytes, types, true, vals)); - } - let desc = format!("(encode?) {}", assert.desc()); - HostTest { desc, asserts } - } - Input::Blob(bytes) => { - let bytes = bytes.to_vec(); - if !assert.pass && assert.right.is_none() { - asserts.push(NotDecode(bytes, types)); - } else { - let mut config = DecoderConfig::new(); - config.set_decoding_quota(DECODING_COST); - let args = - IDLArgs::from_bytes_with_types_with_config(&bytes, env, &types, &config) - .unwrap(); - asserts.push(Decode(bytes.clone(), types.clone(), true, args)); - // round tripping - // asserts.push(Encode(args, types.clone(), true, bytes.clone())); - if let Some(right) = &assert.right { - let expected = right.parse(env, &types).unwrap(); - if let Input::Blob(blob) = right { - asserts.push(Decode( - blob.to_vec(), - types.clone(), - true, - expected.clone(), - )); - } - if !assert.pass { - asserts.push(Decode(bytes, types, assert.pass, expected)); - } - } - } - HostTest { - desc: assert.desc(), - asserts, - } - } - } - } -} - pub fn check(test: Test) -> Result<()> { let mut env = TypeEnv::new(); let prog = IDLProg { diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index a4a7d7daf..7f9f609c6 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,10 +1,12 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ - syntax::{Binding, Dec, IDLArgType, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField}, + syntax::{ + Binding, Dec, IDLArgType, IDLInitArgs, IDLMergedProg, IDLProg, IDLType, PrimType, TypeField, + }, ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; pub struct Env<'a> { @@ -151,29 +153,6 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { Ok(()) } -fn check_cycle(env: &TypeEnv) -> Result<()> { - fn has_cycle<'a>(seen: &mut BTreeSet<&'a str>, env: &'a TypeEnv, t: &'a Type) -> Result { - match t.as_ref() { - TypeInner::Var(id) => { - if seen.insert(id) { - let ty = env.find_type(id)?; - has_cycle(seen, env, ty) - } else { - Ok(true) - } - } - _ => Ok(false), - } - } - for (id, ty) in env.0.iter() { - let mut seen = BTreeSet::new(); - if has_cycle(&mut seen, env, ty)? { - return Err(Error::msg(format!("{id} has cyclic type definition"))); - } - } - Ok(()) -} - fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, typ: _ }) = dec { @@ -185,7 +164,7 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { } env.pre = true; check_defs(env, decs)?; - check_cycle(env.te)?; + env.te.check_cycle()?; env.pre = false; check_defs(env, decs)?; Ok(()) @@ -313,7 +292,7 @@ fn merge_actor( } } -fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> { +fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option, IDLMergedProg)> { let base = if file.is_absolute() { file.parent().unwrap().to_path_buf() } else { @@ -323,13 +302,17 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> .unwrap() .to_path_buf() }; - let prog = - std::fs::read_to_string(file).map_err(|_| Error::msg(format!("Cannot open {file:?}")))?; - let prog = if is_pretty { - pretty_parse_idl_prog(file.to_str().unwrap(), &prog)? - } else { - parse_idl_prog(&prog)? + + let prog = { + let prog = std::fs::read_to_string(file) + .map_err(|_| Error::msg(format!("Cannot open {file:?}")))?; + if is_pretty { + pretty_parse_idl_prog(file.to_str().unwrap(), &prog)? + } else { + parse_idl_prog(&prog)? + } }; + let mut visited = BTreeMap::new(); let mut imports = Vec::new(); load_imports(is_pretty, &base, &mut visited, &prog, &mut imports)?; @@ -340,33 +323,43 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> None => unreachable!(), }) .collect(); + let mut te = TypeEnv::new(); let mut env = Env { te: &mut te, pre: false, }; + let mut idl_merged_prog = IDLMergedProg::new(); + let mut actor: Option = None; for (include_serv, path, name) in imports.iter() { let code = std::fs::read_to_string(path)?; let code = parse_idl_prog(&code)?; check_decs(&mut env, &code.decs)?; + idl_merged_prog.add_decs(&code.decs); if *include_serv { let t = check_actor(&env, &code.actor)?; actor = merge_actor(&env, &actor, &t, name)?; } } + check_decs(&mut env, &prog.decs)?; + idl_merged_prog.add_decs(&prog.decs); + let mut res = check_actor(&env, &prog.actor)?; if actor.is_some() { res = merge_actor(&env, &res, &actor, "")?; } - Ok((te, res)) + + idl_merged_prog.set_actor(res.clone().map(|t| env.te.as_idl_type(&t))); + + Ok((te, res, idl_merged_prog)) } /// Type check did file including the imports. -pub fn check_file(file: &Path) -> Result<(TypeEnv, Option)> { +pub fn check_file(file: &Path) -> Result<(TypeEnv, Option, IDLMergedProg)> { check_file_(file, false) } -pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, Option)> { +pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, Option, IDLMergedProg)> { check_file_(file, true) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 5074a87ee..89c870437 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,6 +1,9 @@ use crate::{check_prog, pretty_check_file, pretty_parse_idl_prog, Error, Result}; use candid::{ - types::{Type, TypeInner}, + types::{ + syntax::{Binding, IDLMergedProg, IDLType}, + Type, TypeInner, + }, TypeEnv, }; use std::path::Path; @@ -11,14 +14,15 @@ pub enum CandidSource<'a> { } impl CandidSource<'_> { - pub fn load(&self) -> Result<(TypeEnv, Option)> { + pub fn load(&self) -> Result<(TypeEnv, Option, IDLMergedProg)> { Ok(match self { CandidSource::File(path) => pretty_check_file(path)?, CandidSource::Text(str) => { let ast = pretty_parse_idl_prog("", str)?; let mut env = TypeEnv::new(); let actor = check_prog(&mut env, &ast)?; - (env, actor) + let idl_merged_prog = IDLMergedProg::from(ast); + (env, actor, idl_merged_prog) } }) } @@ -26,9 +30,9 @@ impl CandidSource<'_> { /// Check compatibility of two service types pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { - let (mut env, t1) = new.load()?; + let (mut env, t1, _) = new.load()?; let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; - let (env2, t2) = old.load()?; + let (env2, t2, _) = old.load()?; let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); @@ -38,9 +42,9 @@ pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { /// Check structural equality of two service types pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { - let (mut env, t1) = left.load()?; + let (mut env, t1, _) = left.load()?; let t1 = t1.ok_or_else(|| Error::msg("left interface has no main service type"))?; - let (env2, t2) = right.load()?; + let (env2, t2, _) = right.load()?; let t2 = t2.ok_or_else(|| Error::msg("right interface has no main service type"))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); @@ -52,7 +56,7 @@ 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, (TypeEnv, Type))> { - let (env, serv) = candid.load()?; + 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)?; Ok(match serv.as_ref() { @@ -64,22 +68,26 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, _ => unreachable!(), }) } -pub fn get_metadata(env: &TypeEnv, serv: &Option) -> Option { - 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, +pub fn get_metadata(env: &IDLMergedProg) -> Option { + let serv = env.actor.as_ref()?; + let serv = env.trace_type(serv).ok()?; + let serv = match &serv { + IDLType::ClassT(_, ty) => ty.as_ref(), + IDLType::ServT(_) => &serv, _ => unreachable!(), }; - let def_list = crate::bindings::analysis::chase_actor(env, &serv).ok()?; - let mut filtered = TypeEnv::new(); + let def_list = crate::bindings::analysis::chase_actor(env, serv).ok()?; + let mut filtered = IDLMergedProg::new(); for d in def_list { - if let Some(t) = env.0.get(d) { - filtered.0.insert(d.to_string(), t.clone()); + if let Ok(typ) = env.find_type(d) { + filtered.insert_binding(Binding { + id: d.to_string(), + typ: typ.clone(), + }); } } - Some(candid::pretty::candid::compile(&filtered, &Some(serv))) + filtered.set_actor(Some(serv.clone())); + Some(candid::pretty::candid::compile(&filtered)) } /// Merge canister metadata candid:args and candid:service into a service constructor. @@ -88,7 +96,7 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { use crate::{parse_idl_init_args, typing::check_init_args}; use candid::types::TypeInner; let candid = CandidSource::Text(candid); - let (env, serv) = candid.load()?; + 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)?; match serv.as_ref() { diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index 412ee8514..c72dd1cb6 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -2,8 +2,8 @@ import type { Principal } from '@dfinity/principal'; 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 type List = [] | [[bigint, List]]; export interface _SERVICE { 'get' : ActorMethod<[], List>, 'set' : ActorMethod<[List], List>, diff --git a/rust/candid_parser/tests/assets/ok/class.did b/rust/candid_parser/tests/assets/ok/class.did index 4850a508e..645c002b0 100644 --- a/rust/candid_parser/tests/assets/ok/class.did +++ b/rust/candid_parser/tests/assets/ok/class.did @@ -1,5 +1,5 @@ -type List = opt record { int; List }; type Profile = record { age : nat8; name : text }; +type List = opt record { int; List }; service : (int, l : List, Profile) -> { get : () -> (List); set : (List) -> (List); diff --git a/rust/candid_parser/tests/assets/ok/class.mo b/rust/candid_parser/tests/assets/ok/class.mo index 41ab07b63..b578beba3 100644 --- a/rust/candid_parser/tests/assets/ok/class.mo +++ b/rust/candid_parser/tests/assets/ok/class.mo @@ -2,8 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type List = ?(Int, List); public type Profile = { age : Nat8; name : Text }; + public type List = ?(Int, List); public type Self = (Int, l : List, Profile) -> async actor { get : shared () -> async List; set : shared List -> async List; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts index d75b132a3..42390de9d 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.d.ts +++ b/rust/candid_parser/tests/assets/ok/cyclic.d.ts @@ -3,8 +3,8 @@ import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; export type A = [] | [B]; -export type B = [] | [C]; export type C = A; +export type B = [] | [C]; export type X = Y; export type Y = Z; export type Z = A; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.did b/rust/candid_parser/tests/assets/ok/cyclic.did index eebf3369f..fd0c4b40e 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.did +++ b/rust/candid_parser/tests/assets/ok/cyclic.did @@ -1,6 +1,6 @@ type A = opt B; -type B = opt C; type C = A; +type B = opt C; type X = Y; type Y = Z; type Z = A; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.mo b/rust/candid_parser/tests/assets/ok/cyclic.mo index 4ca4a600a..6c7d6f0a4 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.mo +++ b/rust/candid_parser/tests/assets/ok/cyclic.mo @@ -3,8 +3,8 @@ module { public type A = ?B; - public type B = ?C; public type C = A; + public type B = ?C; public type X = Y; public type Y = Z; public type Z = A; diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index a3975f37b..89620882c 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -4,14 +4,22 @@ import type { IDL } from '@dfinity/candid'; export type A = B; export type B = [] | [A]; -export type List = [] | [{ 'head' : bigint, 'tail' : List }]; +export interface node { 'head' : bigint, 'tail' : list } +export type list = [] | [node]; +export type tree = { + 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } + } | + { 'leaf' : bigint }; +export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } +export type t = ActorMethod<[Principal], undefined>; +export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; +export type b = [bigint, bigint]; export type a = { 'a' : null } | { 'b' : b }; -export type b = [bigint, bigint]; -export interface broker { 'find' : ActorMethod<[string], Principal> } -export type f = ActorMethod<[List, [Principal, string]], [[] | [List], res]>; -export type list = [] | [node]; export type my_type = Principal; +export type List = [] | [{ 'head' : bigint, 'tail' : List }]; +export type f = ActorMethod<[List, [Principal, string]], [[] | [List], res]>; +export interface broker { 'find' : ActorMethod<[string], Principal> } export interface nested { _0_ : bigint, _1_ : bigint, @@ -24,18 +32,10 @@ export interface nested { { 'C' : null }, _42_ : bigint, } -export type nested_res = { 'Ok' : { 'Ok' : null } | { 'Err' : null } } | - { 'Err' : { 'Ok' : { 'content' : string } } | { 'Err' : [bigint] } }; -export interface node { 'head' : bigint, 'tail' : list } export type res = { 'Ok' : [bigint, bigint] } | { 'Err' : { 'error' : string } }; -export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } -export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; -export type t = ActorMethod<[Principal], undefined>; -export type tree = { - 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } - } | - { 'leaf' : bigint }; +export type nested_res = { 'Ok' : { 'Ok' : null } | { 'Err' : null } } | + { 'Err' : { 'Ok' : { 'content' : string } } | { 'Err' : [bigint] } }; export interface _SERVICE { 'bbbbb' : ActorMethod<[b], undefined>, 'f' : t, diff --git a/rust/candid_parser/tests/assets/ok/example.did b/rust/candid_parser/tests/assets/ok/example.did index 6bd434f09..0767aee59 100644 --- a/rust/candid_parser/tests/assets/ok/example.did +++ b/rust/candid_parser/tests/assets/ok/example.did @@ -1,14 +1,22 @@ type A = B; type B = opt A; -type List = opt record { head : int; tail : List }; -type a = variant { a; b : b }; +type node = record { head : nat; tail : list }; +type list = opt node; +type tree = variant { + branch : record { val : int; left : tree; right : tree }; + leaf : int; +}; +type s = service { f : t; g : (list) -> (B, tree, stream) }; +type t = func (server : s) -> (); +type stream = opt record { head : nat; next : func () -> (stream) query }; type b = record { int; nat }; +type a = variant { a; b : b }; +type my_type = principal; +type List = opt record { head : int; tail : List }; +type f = func (List, func (int32) -> (int64)) -> (opt List, res); type broker = service { find : (name : text) -> (service { current : () -> (nat32); up : () -> () }); }; -type f = func (List, func (int32) -> (int64)) -> (opt List, res); -type list = opt node; -type my_type = principal; type nested = record { 0 : nat; 1 : nat; @@ -18,19 +26,11 @@ type nested = record { 41 : variant { 42; A; B; C }; 42 : nat; }; +type res = variant { Ok : record { int; nat }; Err : record { error : text } }; type nested_res = variant { Ok : variant { Ok; Err }; Err : variant { Ok : record { content : text }; Err : record { int } }; }; -type node = record { head : nat; tail : list }; -type res = variant { Ok : record { int; nat }; Err : record { error : text } }; -type s = service { f : t; g : (list) -> (B, tree, stream) }; -type stream = opt record { head : nat; next : func () -> (stream) query }; -type t = func (server : s) -> (); -type tree = variant { - branch : record { val : int; left : tree; right : tree }; - leaf : int; -}; service : { bbbbb : (b) -> (); f : t; diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 76bb1f058..3a909f1ca 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -4,21 +4,29 @@ module { public type A = B; public type B = ?A; - public type List = ?{ head : Int; tail : List }; - public type a = { #a; #b : b }; + public type node = { head : Nat; tail : list }; + public type list = ?node; + public type tree = { + #branch : { val : Int; left : tree; right : tree }; + #leaf : Int; + }; + public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; + public type t = shared (server : s) -> async (); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type b = (Int, Nat); + public type a = { #a; #b : b }; + public type my_type = Principal; + public type List = ?{ head : Int; tail : List }; + public type f = shared (List, shared Int32 -> async Int64) -> async ( + ?List, + res, + ); public type broker = actor { find : shared (name : Text) -> async actor { current : shared () -> async Nat32; up : shared () -> async (); }; }; - public type f = shared (List, shared Int32 -> async Int64) -> async ( - ?List, - res, - ); - public type list = ?node; - public type my_type = Principal; public type nested = { _0_ : Nat; _1_ : Nat; @@ -28,19 +36,11 @@ module { _41_ : { #_42_ ; #A; #B; #C }; _42_ : Nat; }; + public type res = { #Ok : (Int, Nat); #Err : { error : Text } }; public type nested_res = { #Ok : { #Ok; #Err }; #Err : { #Ok : { content : Text }; #Err : { _0_ : Int } }; }; - public type node = { head : Nat; tail : list }; - public type res = { #Ok : (Int, Nat); #Err : { error : Text } }; - public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared (server : s) -> async (); - public type tree = { - #branch : { val : Int; left : tree; right : tree }; - #leaf : Int; - }; public type Self = actor { bbbbb : shared b -> async (); f : t; diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index c4a9d4002..14ec4611d 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -134,8 +134,8 @@ pub const service : Service = Service(CANISTER_ID); #[test] fn test_Arc_MyList_() { // Generated from ListInner.record.tail.use_type = "Arc" - let candid_src = r#"type List = opt ListInner; -type ListInner = record { head : int; tail : List }; + let candid_src = r#"type ListInner = record { head : int; tail : List }; +type List = opt ListInner; (List)"#; candid_parser::utils::check_rust_type::>(candid_src).unwrap(); } diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts index 53e68afe1..5853eff15 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts +++ b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts @@ -2,8 +2,8 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export interface non_tuple { _1_ : string, _2_ : string } export type tuple = [string, string]; +export interface non_tuple { _1_ : string, _2_ : string } export interface _SERVICE { 'bab' : ActorMethod<[bigint, bigint], undefined>, 'bar' : ActorMethod<[{ '2' : bigint }], { 'e20' : null } | { 'e30' : null }>, diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.did b/rust/candid_parser/tests/assets/ok/fieldnat.did index 921423bb4..1f5e2a585 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.did +++ b/rust/candid_parser/tests/assets/ok/fieldnat.did @@ -1,5 +1,5 @@ -type non_tuple = record { 1 : text; 2 : text }; type tuple = record { text; text }; +type non_tuple = record { 1 : text; 2 : text }; service : { bab : (two : int, nat) -> (); bar : (record { "2" : int }) -> (variant { e20; e30 }); diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.mo b/rust/candid_parser/tests/assets/ok/fieldnat.mo index f38c0359f..d81c131bb 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.mo +++ b/rust/candid_parser/tests/assets/ok/fieldnat.mo @@ -2,8 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type non_tuple = { _1_ : Text; _2_ : Text }; public type tuple = (Text, Text); + public type non_tuple = { _1_ : Text; _2_ : Text }; public type Self = actor { bab : shared (two : Int, Nat) -> async (); bar : shared { _50_ : Int } -> async { #e20; #e30 }; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index 687fa6900..5be20d45f 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts +++ b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts @@ -4,13 +4,13 @@ import type { IDL } from '@dfinity/candid'; export type Fn = ActorMethod<[bigint], bigint>; export type Gn = Fn; +export interface RInline { 'x' : bigint, 'fn' : [Principal, string] } export interface R { 'x' : bigint, 'fn' : [Principal, string], 'gn' : [Principal, string], 'nested' : { 'fn' : [Principal, string] }, } -export interface RInline { 'x' : bigint, 'fn' : [Principal, string] } export interface _SERVICE { 'add_two' : ActorMethod<[bigint], bigint>, 'fn' : Fn, diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.did b/rust/candid_parser/tests/assets/ok/inline_methods.did index 6e568643f..7a0589bd5 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.did +++ b/rust/candid_parser/tests/assets/ok/inline_methods.did @@ -1,7 +1,7 @@ type Fn = func (nat) -> (nat) query; type Gn = Fn; -type R = record { x : nat; fn : Fn; gn : Gn; nested : record { fn : Gn } }; type RInline = record { x : nat; fn : func (nat) -> (nat) query }; +type R = record { x : nat; fn : Fn; gn : Gn; nested : record { fn : Gn } }; service : { add_two : (nat) -> (nat); fn : Fn; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.mo b/rust/candid_parser/tests/assets/ok/inline_methods.mo index b1c8b0168..64cb85e21 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.mo +++ b/rust/candid_parser/tests/assets/ok/inline_methods.mo @@ -4,8 +4,8 @@ module { public type Fn = shared query Nat -> async Nat; public type Gn = Fn; - public type R = { x : Nat; fn : Fn; gn : Gn; nested : { fn : Gn } }; public type RInline = { x : Nat; fn : shared query Nat -> async Nat }; + public type R = { x : Nat; fn : Fn; gn : Gn; nested : { fn : Gn } }; public type Self = actor { add_two : shared Nat -> async Nat; fn : Fn; diff --git a/rust/candid_parser/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 22d1c24a6..d80db1608 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.d.ts +++ b/rust/candid_parser/tests/assets/ok/keyword.d.ts @@ -2,16 +2,16 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +export type o = [] | [o]; +export interface node { 'head' : bigint, 'tail' : list } +export type list = [] | [node]; export type if_ = { 'branch' : { 'val' : bigint, 'left' : if_, 'right' : if_ } } | { 'leaf' : bigint }; -export type list = [] | [node]; -export interface node { 'head' : bigint, 'tail' : list } -export type o = [] | [o]; export interface return_ { 'f' : t, 'g' : ActorMethod<[list], [if_, stream]> } -export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; export type t = ActorMethod<[Principal], undefined>; +export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; export interface _SERVICE { 'Oneway' : ActorMethod<[], undefined>, 'f_' : ActorMethod<[o], o>, diff --git a/rust/candid_parser/tests/assets/ok/keyword.did b/rust/candid_parser/tests/assets/ok/keyword.did index 43965aefa..9b1d2cec1 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.did +++ b/rust/candid_parser/tests/assets/ok/keyword.did @@ -1,13 +1,13 @@ +type o = opt o; +type node = record { head : nat; tail : list }; +type list = opt node; type if = variant { branch : record { val : int; left : if; right : if }; leaf : int; }; -type list = opt node; -type node = record { head : nat; tail : list }; -type o = opt o; type return = service { f : t; g : (list) -> (if, stream) }; -type stream = opt record { head : nat; next : func () -> (stream) query }; type t = func (server : return) -> (); +type stream = opt record { head : nat; next : func () -> (stream) query }; service : { Oneway : () -> () oneway; f_ : (o) -> (o); diff --git a/rust/candid_parser/tests/assets/ok/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo index 408ff6f82..f212f52a5 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.mo +++ b/rust/candid_parser/tests/assets/ok/keyword.mo @@ -2,16 +2,16 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type o = ?o; + public type node = { head : Nat; tail : list }; + public type list = ?node; public type if_ = { #branch : { val : Int; left : if_; right : if_ }; #leaf : Int; }; - public type list = ?node; - public type node = { head : Nat; tail : list }; - public type o = ?o; public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type t = shared (server : return_) -> async (); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type Self = actor { Oneway : shared () -> (); f__ : shared o -> async o; diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index ca60e354d..66e4babca 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -2,11 +2,9 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export type bitcoin_address = string; -export type bitcoin_network = { 'mainnet' : null } | - { 'testnet' : null }; -export type block_hash = Uint8Array | number[]; export type canister_id = Principal; +export type user_id = Principal; +export type wasm_module = Uint8Array | number[]; export interface canister_settings { 'freezing_threshold' : [] | [bigint], 'controllers' : [] | [Array], @@ -19,14 +17,23 @@ export interface definite_canister_settings { 'memory_allocation' : bigint, 'compute_allocation' : bigint, } -export type ecdsa_curve = { 'secp256k1' : null }; -export interface get_balance_request { - 'network' : bitcoin_network, - 'address' : bitcoin_address, - 'min_confirmations' : [] | [number], +export interface http_header { 'value' : string, 'name' : string } +export interface http_response { + 'status' : bigint, + 'body' : Uint8Array | number[], + 'headers' : Array, } -export interface get_current_fee_percentiles_request { - 'network' : bitcoin_network, +export type ecdsa_curve = { 'secp256k1' : null }; +export type satoshi = bigint; +export type bitcoin_network = { 'mainnet' : null } | + { 'testnet' : null }; +export type bitcoin_address = string; +export type block_hash = Uint8Array | number[]; +export interface outpoint { 'txid' : Uint8Array | number[], 'vout' : number } +export interface utxo { + 'height' : number, + 'value' : satoshi, + 'outpoint' : outpoint, } export interface get_utxos_request { 'network' : bitcoin_network, @@ -36,32 +43,25 @@ export interface get_utxos_request { ], 'address' : bitcoin_address, } +export interface get_current_fee_percentiles_request { + 'network' : bitcoin_network, +} export interface get_utxos_response { 'next_page' : [] | [Uint8Array | number[]], 'tip_height' : number, 'tip_block_hash' : block_hash, 'utxos' : Array, } -export interface http_header { 'value' : string, 'name' : string } -export interface http_response { - 'status' : bigint, - 'body' : Uint8Array | number[], - 'headers' : Array, +export interface get_balance_request { + 'network' : bitcoin_network, + 'address' : bitcoin_address, + 'min_confirmations' : [] | [number], } -export type millisatoshi_per_byte = bigint; -export interface outpoint { 'txid' : Uint8Array | number[], 'vout' : number } -export type satoshi = bigint; export interface send_transaction_request { 'transaction' : Uint8Array | number[], 'network' : bitcoin_network, } -export type user_id = Principal; -export interface utxo { - 'height' : number, - 'value' : satoshi, - 'outpoint' : outpoint, -} -export type wasm_module = Uint8Array | number[]; +export type millisatoshi_per_byte = bigint; export interface _SERVICE { 'bitcoin_get_balance' : ActorMethod<[get_balance_request], satoshi>, 'bitcoin_get_current_fee_percentiles' : ActorMethod< diff --git a/rust/candid_parser/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did index 4af885072..db9ccda59 100644 --- a/rust/candid_parser/tests/assets/ok/management.did +++ b/rust/candid_parser/tests/assets/ok/management.did @@ -1,7 +1,6 @@ -type bitcoin_address = text; -type bitcoin_network = variant { mainnet; testnet }; -type block_hash = blob; type canister_id = principal; +type user_id = principal; +type wasm_module = blob; type canister_settings = record { freezing_threshold : opt nat; controllers : opt vec principal; @@ -14,40 +13,41 @@ type definite_canister_settings = record { memory_allocation : nat; compute_allocation : nat; }; -type ecdsa_curve = variant { secp256k1 }; -type get_balance_request = record { - network : bitcoin_network; - address : bitcoin_address; - min_confirmations : opt nat32; +type http_header = record { value : text; name : text }; +type http_response = record { + status : nat; + body : blob; + headers : vec http_header; }; -type get_current_fee_percentiles_request = record { network : bitcoin_network }; +type ecdsa_curve = variant { secp256k1 }; +type satoshi = nat64; +type bitcoin_network = variant { mainnet; testnet }; +type bitcoin_address = text; +type block_hash = blob; +type outpoint = record { txid : blob; vout : nat32 }; +type utxo = record { height : nat32; value : satoshi; outpoint : outpoint }; type get_utxos_request = record { network : bitcoin_network; filter : opt variant { page : blob; min_confirmations : nat32 }; address : bitcoin_address; }; +type get_current_fee_percentiles_request = record { network : bitcoin_network }; type get_utxos_response = record { next_page : opt blob; tip_height : nat32; tip_block_hash : block_hash; utxos : vec utxo; }; -type http_header = record { value : text; name : text }; -type http_response = record { - status : nat; - body : blob; - headers : vec http_header; +type get_balance_request = record { + network : bitcoin_network; + address : bitcoin_address; + min_confirmations : opt nat32; }; -type millisatoshi_per_byte = nat64; -type outpoint = record { txid : blob; vout : nat32 }; -type satoshi = nat64; type send_transaction_request = record { transaction : blob; network : bitcoin_network; }; -type user_id = principal; -type utxo = record { height : nat32; value : satoshi; outpoint : outpoint }; -type wasm_module = blob; +type millisatoshi_per_byte = nat64; service : { bitcoin_get_balance : (get_balance_request) -> (satoshi); bitcoin_get_current_fee_percentiles : ( diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 0609c7387..1ee97e7f7 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -2,10 +2,9 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type bitcoin_address = Text; - public type bitcoin_network = { #mainnet; #testnet }; - public type block_hash = Blob; public type canister_id = Principal; + public type user_id = Principal; + public type wasm_module = Blob; public type canister_settings = { freezing_threshold : ?Nat; controllers : ?[Principal]; @@ -18,42 +17,43 @@ module { memory_allocation : Nat; compute_allocation : Nat; }; + public type http_header = { value : Text; name : Text }; + public type http_response = { + status : Nat; + body : Blob; + headers : [http_header]; + }; public type ecdsa_curve = { #secp256k1 }; - public type get_balance_request = { + public type satoshi = Nat64; + public type bitcoin_network = { #mainnet; #testnet }; + public type bitcoin_address = Text; + public type block_hash = Blob; + public type outpoint = { txid : Blob; vout : Nat32 }; + public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; + public type get_utxos_request = { network : bitcoin_network; + filter : ?{ #page : Blob; #min_confirmations : Nat32 }; address : bitcoin_address; - min_confirmations : ?Nat32; }; public type get_current_fee_percentiles_request = { network : bitcoin_network; }; - public type get_utxos_request = { - network : bitcoin_network; - filter : ?{ #page : Blob; #min_confirmations : Nat32 }; - address : bitcoin_address; - }; public type get_utxos_response = { next_page : ?Blob; tip_height : Nat32; tip_block_hash : block_hash; utxos : [utxo]; }; - public type http_header = { value : Text; name : Text }; - public type http_response = { - status : Nat; - body : Blob; - headers : [http_header]; + public type get_balance_request = { + network : bitcoin_network; + address : bitcoin_address; + min_confirmations : ?Nat32; }; - public type millisatoshi_per_byte = Nat64; - public type outpoint = { txid : Blob; vout : Nat32 }; - public type satoshi = Nat64; public type send_transaction_request = { transaction : Blob; network : bitcoin_network; }; - public type user_id = Principal; - public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; - public type wasm_module = Blob; + public type millisatoshi_per_byte = Nat64; public type Self = actor { bitcoin_get_balance : shared get_balance_request -> async satoshi; bitcoin_get_current_fee_percentiles : shared get_current_fee_percentiles_request -> async [ diff --git a/rust/candid_parser/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 062ef95a3..7fb159c38 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursion.d.ts @@ -4,15 +4,15 @@ import type { IDL } from '@dfinity/candid'; export type A = B; export type B = [] | [A]; -export type list = [] | [node]; export interface node { 'head' : bigint, 'tail' : list } -export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } -export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; -export type t = ActorMethod<[Principal], undefined>; +export type list = [] | [node]; export type tree = { 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } } | { 'leaf' : bigint }; +export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } +export type t = ActorMethod<[Principal], undefined>; +export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; export interface _SERVICE extends s {} export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/recursion.did b/rust/candid_parser/tests/assets/ok/recursion.did index bc4278a62..623a3296a 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.did +++ b/rust/candid_parser/tests/assets/ok/recursion.did @@ -1,12 +1,12 @@ type A = B; type B = opt A; -type list = opt node; type node = record { head : nat; tail : list }; -type s = service { f : t; g : (list) -> (B, tree, stream) }; -type stream = opt record { head : nat; next : func () -> (stream) query }; -type t = func (server : s) -> (); +type list = opt node; type tree = variant { branch : record { val : int; left : tree; right : tree }; leaf : int; }; +type s = service { f : t; g : (list) -> (B, tree, stream) }; +type t = func (server : s) -> (); +type stream = opt record { head : nat; next : func () -> (stream) query }; service : s diff --git a/rust/candid_parser/tests/assets/ok/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo index 6cc51fe02..675ecc038 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.mo +++ b/rust/candid_parser/tests/assets/ok/recursion.mo @@ -4,14 +4,14 @@ module { public type A = B; public type B = ?A; - public type list = ?node; public type node = { head : Nat; tail : list }; - public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared (server : s) -> async (); + public type list = ?node; public type tree = { #branch : { val : Int; left : tree; right : tree }; #leaf : Int; }; + public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; + public type t = shared (server : s) -> async (); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type Self = s } diff --git a/rust/candid_parser/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts index 57c3c871d..097ccc9cc 100644 --- a/rust/candid_parser/tests/assets/ok/service.d.ts +++ b/rust/candid_parser/tests/assets/ok/service.d.ts @@ -2,9 +2,9 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export type Func = ActorMethod<[], Principal>; export interface Service { 'f' : Func } export type Service2 = Service; +export type Func = ActorMethod<[], Principal>; export interface _SERVICE { 'asArray' : ActorMethod<[], [Array, Array<[Principal, string]>]>, 'asPrincipal' : ActorMethod<[], [Principal, [Principal, string]]>, diff --git a/rust/candid_parser/tests/assets/ok/service.did b/rust/candid_parser/tests/assets/ok/service.did index 672d699b5..7dc21afac 100644 --- a/rust/candid_parser/tests/assets/ok/service.did +++ b/rust/candid_parser/tests/assets/ok/service.did @@ -1,6 +1,6 @@ -type Func = func () -> (Service); type Service = service { f : Func }; type Service2 = Service; +type Func = func () -> (Service); service : { asArray : () -> (vec Service2, vec Func) query; asPrincipal : () -> (Service2, Func); diff --git a/rust/candid_parser/tests/assets/ok/service.mo b/rust/candid_parser/tests/assets/ok/service.mo index 14ee15e7a..c4a66823a 100644 --- a/rust/candid_parser/tests/assets/ok/service.mo +++ b/rust/candid_parser/tests/assets/ok/service.mo @@ -2,9 +2,9 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type Func = shared () -> async Service; public type Service = actor { f : Func }; public type Service2 = Service; + public type Func = shared () -> async Service; public type Self = actor { asArray : shared query () -> async ([Service2], [Func]); asPrincipal : shared () -> async (Service2, Func); diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 3129c4afd..d78c8f96b 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -40,10 +40,10 @@ fn compiler_test(resource: &str) { let candid_path = base_path.join(filename); match check_file(&candid_path) { - Ok((env, actor)) => { + Ok((_, _, idl_merged_prog)) => { { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); - let content = compile(&env, &actor); + let content = compile(&idl_merged_prog); // Type check output let ast = parse_idl_prog(&content).unwrap(); check_prog(&mut TypeEnv::new(), &ast).unwrap(); @@ -51,13 +51,14 @@ fn compiler_test(resource: &str) { } { match filename.file_name().unwrap().to_str().unwrap() { - "unicode.did" | "escape.did" => { - check_error(|| motoko::compile(&env, &actor), "not a valid Motoko id") - } + "unicode.did" | "escape.did" => check_error( + || motoko::compile(&idl_merged_prog), + "not a valid Motoko id", + ), _ => { let mut output = mint.new_goldenfile(filename.with_extension("mo")).unwrap(); - let content = motoko::compile(&env, &actor); + let content = motoko::compile(&idl_merged_prog); writeln!(output, "{content}").unwrap(); } } @@ -87,20 +88,20 @@ fn compiler_test(resource: &str) { _ => (), } let mut output = mint.new_goldenfile(filename.with_extension("rs")).unwrap(); - let (content, unused) = rust::compile(&config, &env, &actor, external); + let (content, unused) = rust::compile(&config, &idl_merged_prog, external); assert!(unused.is_empty()); writeln!(output, "{content}").unwrap(); } { let mut output = mint.new_goldenfile(filename.with_extension("js")).unwrap(); - let content = javascript::compile(&env, &actor); + let content = javascript::compile(&idl_merged_prog); writeln!(output, "{content}").unwrap(); } { let mut output = mint .new_goldenfile(filename.with_extension("d.ts")) .unwrap(); - let content = typescript::compile(&env, &actor); + let content = typescript::compile(&idl_merged_prog); writeln!(output, "{content}").unwrap(); } } diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 89959ddd9..5615f01c2 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,12 +1,16 @@ use anyhow::{bail, Result}; -use candid_parser::candid::types::{ - subtype, - syntax::{IDLType, IDLTypes}, - Type, -}; use candid_parser::{ - configs::Configs, parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, - pretty_parse_idl_types, pretty_wrap, typing::ast_to_type, Error, IDLArgs, IDLValue, TypeEnv, + candid::types::{ + subtype, + syntax::{IDLType, IDLTypes}, + Type, + }, + configs::Configs, + parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, pretty_parse_idl_types, + pretty_wrap, + types::syntax::IDLMergedProg, + typing::ast_to_type, + Error, IDLArgs, IDLValue, TypeEnv, }; use clap::Parser; use console::style; @@ -121,33 +125,36 @@ impl TypeAnnotation { fn is_empty(&self) -> bool { self.tys.is_none() && self.method.is_none() } - fn get_types(&self, mode: Mode) -> candid_parser::Result<(TypeEnv, Vec)> { - let (env, actor) = if let Some(ref file) = self.defs { + fn get_types( + &self, + mode: Mode, + ) -> candid_parser::Result<(TypeEnv, Vec, IDLMergedProg, Vec)> { + let (env, _, idl_prog) = if let Some(ref file) = self.defs { pretty_check_file(file)? } else { - (TypeEnv::new(), None) + (TypeEnv::new(), None, IDLMergedProg::new()) }; - match (&self.tys, &self.method) { - (None, None) => Err(Error::msg("no type annotations")), - (Some(tys), None) => { - let mut types = Vec::new(); - for ty in tys.args.iter() { - types.push(ast_to_type(&env, ty)?); - } - Ok((env, types)) - } + let idl_types = match (&self.tys, &self.method) { + (None, None) => return Err(Error::msg("no type annotations")), + (Some(tys), None) => tys.args.clone(), (None, Some(meth)) => { - let actor = actor + let actor = idl_prog + .actor + .as_ref() .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; - let func = env.get_method(&actor, meth)?; - let types = match mode { + let func = idl_prog.get_method(actor, meth).map_err(Error::msg)?; + match mode { Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), Mode::Decode => func.rets.clone(), - }; - Ok((env, types)) + } } _ => unreachable!(), + }; + let mut types = Vec::new(); + for ty in idl_types.iter() { + types.push(ast_to_type(&env, ty)?); } + Ok((env, types, idl_prog, idl_types)) } } @@ -181,9 +188,9 @@ fn main() -> Result<()> { previous, strict, } => { - let (mut env, opt_t1) = pretty_check_file(&input)?; + let (mut env, opt_t1, _) = pretty_check_file(&input)?; if let Some(previous) = previous { - let (env2, opt_t2) = pretty_check_file(&previous)?; + let (env2, opt_t2, _) = pretty_check_file(&previous)?; match (opt_t1, opt_t2) { (Some(t1), Some(t2)) => { let mut gamma = HashSet::new(); @@ -201,10 +208,10 @@ fn main() -> Result<()> { } } Command::Subtype { defs, ty1, ty2 } => { - let (env, _) = if let Some(file) = defs { - pretty_check_file(&file)? + let env = if let Some(file) = defs { + pretty_check_file(&file)?.0 } else { - (TypeEnv::new(), None) + TypeEnv::new() }; let ty1 = ast_to_type(&env, &ty1)?; let ty2 = ast_to_type(&env, &ty2)?; @@ -214,20 +221,15 @@ fn main() -> Result<()> { input, target, config, - methods, + methods: _, } => { let configs = load_config(&config)?; - let (env, mut actor) = pretty_check_file(&input)?; - if !methods.is_empty() { - actor = Some(candid_parser::bindings::analysis::project_methods( - &env, &actor, methods, - )?); - } + let (_, _, idl_env) = pretty_check_file(&input)?; let content = match target.as_str() { - "js" => candid_parser::bindings::javascript::compile(&env, &actor), - "ts" => candid_parser::bindings::typescript::compile(&env, &actor), - "did" => candid_parser::pretty::candid::compile(&env, &actor), - "mo" => candid_parser::bindings::motoko::compile(&env, &actor), + "js" => candid_parser::bindings::javascript::compile(&idl_env), + "ts" => candid_parser::bindings::typescript::compile(&idl_env), + "did" => candid_parser::pretty::candid::compile(&idl_env), + "mo" => candid_parser::bindings::motoko::compile(&idl_env), "rs" => { use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; let external = configs @@ -235,7 +237,7 @@ fn main() -> Result<()> { .map(|x| x.clone().try_into().unwrap()) .unwrap_or(ExternalConfig::default()); let config = Config::new(configs); - let (res, unused) = compile(&config, &env, &actor, external); + let (res, unused) = compile(&config, &idl_env, external); warn_unused(&unused); res } @@ -249,7 +251,7 @@ fn main() -> Result<()> { _ => unreachable!(), }; external.0.insert("target".to_string(), target.to_string()); - let (res, unused) = compile(&config, &env, &actor, external); + let (res, unused) = compile(&config, &idl_env, external); warn_unused(&unused); res } @@ -262,7 +264,7 @@ fn main() -> Result<()> { } Command::Assist { annotate } => { use candid_parser::assist::{input_args, Context}; - let (env, types) = annotate.get_types(Mode::Encode)?; + let (env, types, _, _) = annotate.get_types(Mode::Encode)?; let ctx = Context::new(env.clone()); let args = input_args(&ctx, &types)?; println!("{args}"); @@ -284,7 +286,7 @@ fn main() -> Result<()> { let bytes = if annotate.is_empty() { args.to_bytes()? } else { - let (env, types) = annotate.get_types(Mode::Encode)?; + let (env, types, _, _) = annotate.get_types(Mode::Encode)?; args.to_bytes_with_types(&env, &types)? }; let hex = match format.as_str() { @@ -325,7 +327,7 @@ fn main() -> Result<()> { let value = if annotate.is_empty() { IDLArgs::from_bytes(&bytes)? } else { - let (env, types) = annotate.get_types(Mode::Decode)?; + let (env, types, _, _) = annotate.get_types(Mode::Decode)?; IDLArgs::from_bytes_with_types(&bytes, &env, &types)? }; println!("{value}"); @@ -338,7 +340,7 @@ fn main() -> Result<()> { } => { use candid_parser::configs::{Scope, ScopePos}; use rand::Rng; - let (env, types) = if args.is_some() { + let (_, _, idl_prog, idl_types) = if args.is_some() { annotate.get_types(Mode::Decode)? } else { annotate.get_types(Mode::Encode)? @@ -346,7 +348,7 @@ fn main() -> Result<()> { let config = load_config(&config)?; // TODO figure out how many bytes of entropy we need let seed: Vec = if let Some(ref args) = args { - let (env, types) = annotate.get_types(Mode::Encode)?; + let (env, types, _, _) = annotate.get_types(Mode::Encode)?; let bytes = args.to_bytes_with_types(&env, &types)?; bytes.into_iter().rev().cycle().take(2048).collect() } else { @@ -361,7 +363,7 @@ fn main() -> Result<()> { }); Scope { position, method } }); - let args = candid_parser::random::any(&seed, config, &env, &types, &scope)?; + let args = candid_parser::random::any(&seed, config, &idl_prog, &idl_types, &scope)?; match lang.as_str() { "did" => println!("{args}"), "js" => println!(