diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index ca8cd1636..26bcaaf03 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -188,6 +188,7 @@ impl ConsType { let field = Field { id: Label::Id(f.id).into(), ty: f.index.to_type(len)?, + comment: None, }; res.push(field); } diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index bb1fbf1cb..48a9c38ff 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -923,8 +923,8 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { (TypeInner::Record(ref e), TypeInner::Record(ref w)) => { match (&e[..], &w[..]) { ( - [Field { id: e_id0, ty: ek }, Field { id: e_id1, ty: ev }], - [Field { id: w_id0, ty: wk }, Field { id: w_id1, ty: wv }], + [Field { id: e_id0, ty: ek, .. }, Field { id: e_id1, ty: ev, .. }], + [Field { id: w_id0, ty: wk, .. }, Field { id: w_id1, ty: wv, .. }], ) if **e_id0 == Label::Id(0) && **e_id1 == Label::Id(1) && **w_id0 == Label::Id(0) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 0f3b80ed2..106551947 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -74,10 +74,10 @@ pub(crate) fn pp_text(id: &str) -> RcDoc { } pub fn pp_ty(ty: &Type) -> RcDoc { - pp_ty_inner(ty.as_ref()) + pp_ty_inner(ty.as_ref(), ty.comment()) } -pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { +pub fn pp_ty_inner<'a>(ty: &'a TypeInner, comment: Option<&[String]>) -> RcDoc<'a> { use TypeInner::*; match ty { Null => str("null"), @@ -103,7 +103,7 @@ pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { 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()); + let t = Type::from((ty.clone(), comment)); if t.is_tuple() { let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ";"); kwd("record").append(enclose_space("{", tuple, "}")) @@ -141,7 +141,9 @@ pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { } else { kwd(" :").append(pp_ty(&field.ty)) }; - pp_label(&field.id).append(ty_doc) + pp_comment(field.comment()) + .append(pp_label(&field.id)) + .append(ty_doc) } fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc { @@ -195,7 +197,10 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { TypeInner::Var(_) => pp_ty(func), _ => unreachable!(), }; - pp_text(id).append(kwd(" :")).append(func_doc) + pp_comment(func.comment()) + .append(pp_text(id)) + .append(kwd(" :")) + .append(func_doc) }), ";", ); @@ -204,7 +209,8 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { fn pp_defs(env: &TypeEnv) -> RcDoc { lines(env.0.iter().map(|(id, ty)| { - kwd("type") + pp_comment(ty.comment()) + .append(kwd("type")) .append(ident(id)) .append(kwd("=")) .append(pp_ty(ty)) @@ -220,6 +226,17 @@ fn pp_actor(ty: &Type) -> RcDoc { } } +fn pp_comment(comment_lines: Option<&[String]>) -> RcDoc { + let mut comment_doc = RcDoc::nil(); + if let Some(comment_lines) = comment_lines { + for line in comment_lines { + comment_doc = + comment_doc.append(RcDoc::text("// ").append(line).append(RcDoc::hardline())); + } + } + comment_doc +} + pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [ArgType]) -> RcDoc<'a> { pp_defs(env).append(pp_args(args)) } @@ -228,7 +245,9 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { None => pp_defs(env).pretty(LINE_WIDTH).to_string(), Some(actor) => { let defs = pp_defs(env); - let actor = kwd("service :").append(pp_actor(actor)); + let actor = pp_comment(actor.comment()) + .append(kwd("service :")) + .append(pp_actor(actor)); let doc = defs.append(actor); doc.pretty(LINE_WIDTH).to_string() } diff --git a/rust/candid/src/pretty/utils.rs b/rust/candid/src/pretty/utils.rs index 89e92fdf7..27f0f3cf1 100644 --- a/rust/candid/src/pretty/utils.rs +++ b/rust/candid/src/pretty/utils.rs @@ -54,9 +54,8 @@ pub fn concat<'a, D>(docs: D, sep: &'a str) -> RcDoc<'a> where D: Iterator> + Clone, { - RcDoc::intersperse(docs.clone().map(|d| d.append(sep)), RcDoc::line()).flat_alt( - RcDoc::intersperse(docs, RcDoc::text(sep).append(RcDoc::line())), - ) + RcDoc::intersperse(docs.clone().map(|d| d.append(sep)), RcDoc::line()) + .flat_alt(strict_concat(docs, sep)) } pub fn lines<'a, D>(docs: D) -> RcDoc<'a> diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 53fee18ef..9a7cc0b3f 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -304,7 +304,7 @@ impl TypeSerialize { sleb128_encode(&mut buf, Opcode::Record as i64)?; leb128_encode(&mut buf, fs.len() as u64)?; - for Field { id, ty } in fs { + for Field { id, ty, .. } in fs { leb128_encode(&mut buf, u64::from(id.get_id()))?; self.encode(&mut buf, ty)?; } @@ -316,7 +316,7 @@ impl TypeSerialize { sleb128_encode(&mut buf, Opcode::Variant as i64)?; leb128_encode(&mut buf, fs.len() as u64)?; - for Field { id, ty } in fs { + for Field { id, ty, .. } in fs { leb128_encode(&mut buf, u64::from(id.get_id()))?; self.encode(&mut buf, ty)?; } diff --git a/rust/candid/src/types/impls.rs b/rust/candid/src/types/impls.rs index 0682a4659..e0f5c65f7 100644 --- a/rust/candid/src/types/impls.rs +++ b/rust/candid/src/types/impls.rs @@ -193,10 +193,12 @@ macro_rules! map_impl { Field { id: Label::Id(0).into(), ty: K::ty(), + comment: None, }, Field { id: Label::Id(1).into(), ty: V::ty(), + comment: None, }, ]).into(); TypeInner::Vec(tuple).into() @@ -276,10 +278,12 @@ where Field { id: Label::Named("Ok".to_owned()).into(), ty: T::ty(), + comment: None, }, Field { id: Label::Named("Err".to_owned()).into(), ty: E::ty(), + comment: None, }, ]) .into() @@ -470,7 +474,7 @@ macro_rules! tuple_impls { { fn _ty() -> Type { TypeInner::Record(vec![ - $(Field{ id: Label::Id($n).into(), ty: $name::ty() },)+ + $(Field{ id: Label::Id($n).into(), ty: $name::ty(), comment: None },)+ ]).into() } fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> @@ -512,10 +516,12 @@ impl CandidType for std::time::SystemTime { Field { id: Label::Named("nanos_since_epoch".to_owned()).into(), ty: u32::ty(), + comment: None, }, Field { id: Label::Named("secs_since_epoch".to_owned()).into(), ty: u64::ty(), + comment: None, }, ]) .into() @@ -547,10 +553,12 @@ impl CandidType for std::time::Duration { Field { id: Label::Named("secs".to_owned()).into(), ty: u64::ty(), + comment: None, }, Field { id: Label::Named("nanos".to_owned()).into(), ty: u32::ty(), + comment: None, }, ]) .into() diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 4a70cdba0..99b46054f 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -96,19 +96,20 @@ impl TypeContainer { self.go(&t) } fn go(&mut self, t: &Type) -> Type { - match t.as_ref() { + let inner = match t.as_ref() { TypeInner::Opt(t) => TypeInner::Opt(self.go(t)), TypeInner::Vec(t) => TypeInner::Vec(self.go(t)), TypeInner::Record(fs) => { - let res: Type = TypeInner::Record( + let record_inner = TypeInner::Record( fs.iter() - .map(|Field { id, ty }| Field { + .map(|Field { id, ty, comment }| Field { id: id.clone(), ty: self.go(ty), + comment: comment.clone(), }) .collect(), - ) - .into(); + ); + let res = (record_inner, t.comment()).into(); if t.is_tuple() { return res; } @@ -123,15 +124,16 @@ impl TypeContainer { } } TypeInner::Variant(fs) => { - let res: Type = TypeInner::Variant( + let variant_inner = TypeInner::Variant( fs.iter() - .map(|Field { id, ty }| Field { + .map(|Field { id, ty, comment }| Field { id: id.clone(), ty: self.go(ty), + comment: comment.clone(), }) .collect(), - ) - .into(); + ); + let res = (variant_inner, t.comment()).into(); let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { self.env.0.insert(id.to_string(), res); @@ -174,13 +176,50 @@ impl TypeContainer { self.go(ty), ), t => t.clone(), - } - .into() + }; + (inner, t.comment()).into() } } -#[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] -pub struct Type(pub std::rc::Rc); +type CommentLines = Vec; + +#[derive(Clone)] +pub struct Type( + pub std::rc::Rc, + Option>, +); + +impl PartialEq for Type { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for Type {} + +impl std::hash::Hash for Type { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl PartialOrd for Type { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Type { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl std::fmt::Debug for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] pub enum TypeInner { @@ -227,7 +266,15 @@ impl AsRef for Type { } impl From for Type { fn from(t: TypeInner) -> Self { - Type(t.into()) + Type(t.into(), None) + } +} +impl From<(TypeInner, Option<&[String]>)> for Type { + fn from((t, comment_lines): (TypeInner, Option<&[String]>)) -> Self { + Type( + t.into(), + comment_lines.map(|s| std::rc::Rc::new(s.to_vec())), + ) } } impl TypeInner { @@ -274,17 +321,19 @@ impl Type { Vec(t) => Vec(t.subst(tau)), Record(fs) => Record( fs.iter() - .map(|Field { id, ty }| Field { + .map(|Field { id, ty, .. }| Field { id: id.clone(), ty: ty.subst(tau), + comment: None, }) .collect(), ), Variant(fs) => Variant( fs.iter() - .map(|Field { id, ty }| Field { + .map(|Field { id, ty, .. }| Field { id: id.clone(), ty: ty.subst(tau), + comment: None, }) .collect(), ), @@ -321,6 +370,9 @@ impl Type { } .into() } + pub fn comment(&self) -> Option<&[String]> { + self.1.as_ref().map(|rc| rc.as_slice()) + } } #[cfg(feature = "printer")] impl fmt::Display for Type { @@ -331,7 +383,11 @@ impl fmt::Display for Type { #[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)) + write!( + f, + "{}", + crate::pretty::candid::pp_ty_inner(self, None).pretty(80) + ) } } #[cfg(not(feature = "printer"))] @@ -476,6 +532,7 @@ pub type SharedLabel = std::rc::Rc