From 679ddac0c7838ac33db70a9187d6b81520fee110 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 11 Jun 2025 18:45:21 +0200 Subject: [PATCH 01/23] feat: support line comments (wip) --- rust/candid/src/pretty/candid.rs | 28 +++++++--- rust/candid/src/types/internal.rs | 15 ++++-- rust/candid_parser/src/bindings/motoko.rs | 26 +++++++-- rust/candid_parser/src/bindings/rust.rs | 31 +++++++++-- .../candid_parser/src/bindings/rust_agent.hbs | 3 ++ rust/candid_parser/src/bindings/rust_call.hbs | 3 ++ rust/candid_parser/src/bindings/rust_stub.hbs | 3 ++ rust/candid_parser/src/grammar.lalrpop | 32 ++++++----- rust/candid_parser/src/token.rs | 18 ++++++- rust/candid_parser/src/types.rs | 7 +++ rust/candid_parser/src/typing.rs | 54 +++++++++---------- rust/candid_parser/tests/assets/comment.did | 8 ++- .../candid_parser/tests/assets/management.did | 7 +++ .../tests/assets/ok/comment.d.ts | 3 ++ .../candid_parser/tests/assets/ok/comment.did | 2 + rust/candid_parser/tests/assets/ok/comment.js | 3 ++ rust/candid_parser/tests/assets/ok/comment.mo | 2 + rust/candid_parser/tests/assets/ok/comment.rs | 4 ++ .../tests/assets/ok/management.did | 13 ++++- .../tests/assets/ok/management.mo | 12 ++++- .../tests/assets/ok/management.rs | 7 +++ 21 files changed, 218 insertions(+), 63 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 5f67607de..886c811b1 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -72,10 +72,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.1.clone()) } -pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { +pub fn pp_ty_inner(ty: &TypeInner, comment: Option) -> RcDoc { use TypeInner::*; match ty { Null => str("null"), @@ -101,7 +101,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(ty.clone().into(), comment); if t.is_tuple() { let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ";"); kwd("record").append(enclose_space("{", tuple, "}")) @@ -139,7 +139,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.ty.1.as_ref()) + .append(pp_label(&field.id)) + .append(ty_doc) } fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc { @@ -180,7 +182,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.1.as_ref()) + .append(pp_text(id)) + .append(kwd(" :")) + .append(func_doc) }), ";", ); @@ -189,7 +194,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.1.as_ref()) + .append(kwd("type")) .append(ident(id)) .append(kwd("=")) .append(pp_ty(ty)) @@ -205,6 +211,16 @@ fn pp_actor(ty: &Type) -> RcDoc { } } +fn pp_comment(comment: Option<&String>) -> RcDoc { + let mut comment_doc = RcDoc::nil(); + if let Some(comment) = comment { + for line in comment.lines() { + comment_doc = comment_doc.append(RcDoc::text("// ").append(line).append(RcDoc::line())); + } + } + comment_doc +} + pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [Type]) -> RcDoc<'a> { pp_defs(env).append(pp_args(args)) } diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index ed9003c40..88762ceea 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -166,7 +166,7 @@ impl TypeContainer { } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] -pub struct Type(pub std::rc::Rc); +pub struct Type(pub std::rc::Rc, pub Option); #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] pub enum TypeInner { @@ -213,7 +213,12 @@ 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): (TypeInner, Option<&String>)) -> Self { + Type(t.into(), comment.cloned()) } } impl TypeInner { @@ -302,7 +307,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"))] diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index d0fcdbd49..156710d5b 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -162,7 +162,9 @@ fn pp_field(field: &Field) -> RcDoc { pp_label(&field.id).append(" : ").append(pp_ty(&field.ty)) } fn pp_variant(field: &Field) -> RcDoc { - let doc = str("#").append(pp_label(&field.id)); + let doc = pp_comment(field.ty.1.as_ref()) + .append(str("#")) + .append(pp_label(&field.id)); if *field.ty != TypeInner::Null { doc.append(" : ").append(pp_ty(&field.ty)) } else { @@ -212,8 +214,12 @@ fn pp_args(args: &[Type]) -> RcDoc { fn pp_service(serv: &[(String, Type)]) -> RcDoc { let doc = concat( - serv.iter() - .map(|(id, func)| escape(id, true).append(" : ").append(pp_ty(func))), + serv.iter().map(|(id, func)| { + pp_comment(func.1.as_ref()) + .append(escape(id, true)) + .append(" : ") + .append(pp_ty(func)) + }), ";", ); kwd("actor").append(enclose_space("{", doc, "}")) @@ -221,7 +227,8 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { fn pp_defs(env: &TypeEnv) -> RcDoc { lines(env.0.iter().map(|(id, ty)| { - kwd("public type") + pp_comment(ty.1.as_ref()) + .append(kwd("public type")) .append(escape(id, false)) .append(" = ") .append(pp_ty(ty)) @@ -237,6 +244,17 @@ fn pp_actor(ty: &Type) -> RcDoc { } } +fn pp_comment(comment: Option<&String>) -> RcDoc { + let mut comment_doc = RcDoc::nil(); + if let Some(comment) = comment { + for line in comment.lines() { + comment_doc = + comment_doc.append(RcDoc::text("/// ").append(line).append(RcDoc::line())); + } + } + comment_doc +} + pub fn compile(env: &TypeEnv, actor: &Option) -> 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. diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index c2f58fbbd..8663c8200 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -152,6 +152,16 @@ fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { None => RcDoc::text("pub "), } } +fn pp_comment<'a>(comment: Option<&'a String>) -> RcDoc<'a> { + let mut comment_doc = RcDoc::nil(); + if let Some(comment) = comment { + for line in comment.lines() { + comment_doc = + comment_doc.append(RcDoc::text("/// ").append(line).append(RcDoc::line())); + } + } + comment_doc +} impl<'a> State<'a> { fn generate_test(&mut self, src: &Type, use_type: &str) { @@ -337,10 +347,11 @@ fn test_{test_name}() {{ fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool, is_ref: bool) -> RcDoc<'b> { let lab = field.id.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = self + let f = self .pp_label(&field.id, false, need_vis) .append(kwd(":")) .append(self.pp_ty(&field.ty, is_ref)); + let res = pp_comment(field.ty.1.as_ref()).append(f); self.state.pop_state(old, StateElem::Label(&lab)); res } @@ -369,7 +380,7 @@ fn test_{test_name}() {{ fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { let lab = field.id.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = match field.ty.as_ref() { + let f = match field.ty.as_ref() { TypeInner::Null => self.pp_label(&field.id, true, false), TypeInner::Record(fs) => self .pp_label(&field.id, true, false) @@ -380,6 +391,7 @@ fn test_{test_name}() {{ ")", )), }; + let res = pp_comment(field.ty.1.as_ref()).append(f); self.state.pop_state(old, StateElem::Label(&lab)); res } @@ -482,8 +494,9 @@ fn test_{test_name}() {{ } } }; + let line_with_comment = pp_comment(ty.1.as_ref()).append(line); self.state.pop_state(old, StateElem::Label(id)); - res.push(line) + res.push(line_with_comment) } lines(res.into_iter()) } @@ -538,7 +551,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: &Function, comment: Option<&String>) -> Method { use candid::types::internal::FuncMode; let old = self.state.push_state(&StateElem::Label(id)); let name = self @@ -598,6 +611,12 @@ fn test_{test_name}() {{ .map(|x| x.pretty(LINE_WIDTH).to_string()) .collect(), mode, + comment: comment.map(|c| { + c.lines() + .map(|l| format!("/// {l}")) + .collect::>() + .join("\n") + }), }; self.state.pop_state(old, StateElem::Label(id)); res @@ -632,8 +651,9 @@ fn test_{test_name}() {{ let serv = self.state.env.as_service(&actor).unwrap(); let mut res = Vec::new(); for (id, func) in serv.iter() { + let comment = func.1.as_ref(); let func = self.state.env.as_func(func).unwrap(); - res.push(self.pp_function(id, func)); + res.push(self.pp_function(id, func, comment)); } (res, init) } @@ -652,6 +672,7 @@ pub struct Method { pub args: Vec<(String, String)>, pub rets: Vec, pub mode: String, + pub comment: Option, } pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Output, Vec) { let mut state = NominalState { diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs index 4e0526254..815e895bb 100644 --- a/rust/candid_parser/src/bindings/rust_agent.hbs +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -9,6 +9,9 @@ type Result = std::result::Result; pub struct {{PascalCase service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> {{PascalCase service_name}}<'a> { {{#each methods}} + {{#if this.comment}} + {{this.comment}} + {{/if}} pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: &{{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { let args = Encode!({{#each this.args}}&{{this.0}}{{#unless @last}},{{/unless}}{{/each}})?; let bytes = self.1.{{#if (eq this.mode "update")}}update{{else}}query{{/if}}(&self.0, "{{escape_debug this.original_name}}").with_arg(args).{{#if (eq this.mode "update")}}call_and_wait{{else}}call{{/if}}().await?; diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs index 788fc0db3..80096bcf2 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -9,6 +9,9 @@ use ic_cdk::api::call::CallResult as Result; pub struct {{PascalCase service_name}}(pub Principal); impl {{PascalCase service_name}} { {{#each methods}} + {{#if this.comment}} + {{this.comment}} + {{/if}} pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: &{{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { ic_cdk::call(self.0, "{{escape_debug this.original_name}}", ({{#each this.args}}{{this.0}},{{/each}})).await } diff --git a/rust/candid_parser/src/bindings/rust_stub.hbs b/rust/candid_parser/src/bindings/rust_stub.hbs index a0cf2c4cd..27a4564d3 100644 --- a/rust/candid_parser/src/bindings/rust_stub.hbs +++ b/rust/candid_parser/src/bindings/rust_stub.hbs @@ -11,6 +11,9 @@ fn init({{#each init_args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{this.1}}{{ } {{/if}} {{#each methods}} +{{#if this.comment}} +{{this.comment}} +{{/if}} #[ic_cdk::{{cdk_attribute this.mode this.name this.original_name}}] fn {{this.name}}({{#each this.args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{this.1}}{{/each}}) -> {{vec_to_arity this.rets}} { unimplemented!() diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 5d00fe827..4623276b1 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -1,4 +1,4 @@ -use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs}; +use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, Comment}; use super::test::{Assert, Input, Test}; use super::token::{Token, error2, LexicalError, Span}; use candid::{Principal, types::Label}; @@ -46,6 +46,8 @@ extern { ";" => Token::Semi, ":" => Token::Colon, "->" => Token::Arrow, + "line_comment" => Token::LineComment(), + "\n" => Token::Newline, } } @@ -174,7 +176,7 @@ pub Typ: IDLType = { Label::Unnamed(_) => { id = id + 1; Label::Unnamed(id - 1) }, ref l => { id = l.get_id() + 1; l.clone() }, }; - TypeField { label, typ: f.typ.clone() } + TypeField { label, typ: f.typ.clone(), comment: f.comment.clone() } }).collect(); fs.sort_unstable_by_key(|TypeField { label, .. }| label.get_id()); check_unique(fs.iter().map(|f| &f.label)).map_err(|e| error2(e, span))?; @@ -202,19 +204,19 @@ PrimTyp: IDLType = { } FieldTyp: TypeField = { - ":" =>? Ok(TypeField { label: Label::Id(n), typ: t }), - ":" => TypeField { label: Label::Named(n), typ: t }, + ":" =>? Ok(TypeField { label: Label::Id(n), typ: t, comment }), + ":" => TypeField { label: Label::Named(n), typ: t, comment }, } RecordFieldTyp: TypeField = { FieldTyp => <>, - Typ => TypeField { label: Label::Unnamed(0), typ: <> }, + Typ => TypeField { label: Label::Unnamed(0), typ: <>, comment: None }, } VariantFieldTyp: TypeField = { FieldTyp => <>, - Name => TypeField { label: Label::Named(<>), typ: IDLType::PrimT(PrimType::Null) }, - FieldId =>? Ok(TypeField { label: Label::Id(<>), typ: IDLType::PrimT(PrimType::Null) }), + => TypeField { label: Label::Named(n), typ: IDLType::PrimT(PrimType::Null), comment }, + =>? Ok(TypeField { label: Label::Id(n), typ: IDLType::PrimT(PrimType::Null), comment }), } TupTyp: Vec = "(" > ")" => <>; @@ -245,15 +247,15 @@ ActorTyp: Vec = { } MethTyp: Binding = { - ":" => Binding { id: n, typ: IDLType::FuncT(f) }, - ":" => Binding { id: n, typ: IDLType::VarT(id) }, + ":" => Binding { id: n, typ: IDLType::FuncT(f), comment }, + ":" => Binding { id: n, typ: IDLType::VarT(id), comment }, } // Type declarations Def: Dec = { - "type" "=" => Dec::TypD(Binding { id: id, typ: t }), - "import" => Dec::ImportType(<>), - "import" "service" => Dec::ImportServ(<>), + "type" "=" => Dec::TypD(Binding { id, typ, comment }), + Comment? "import" => Dec::ImportType(<>), + Comment? "import" "service" => Dec::ImportServ(<>), } Actor: IDLType = { @@ -281,7 +283,7 @@ Input: Input = { Bytes => Input::Blob(<>), } -Assert: Assert = > =>? { +Assert: Assert = Comment? > =>? { if id.0 != "assert" { Err(error2("not an assert", id.1)) } else { Ok(assert) } @@ -304,6 +306,10 @@ Name: String = { Text => <>, } +Comment: Comment = { + "line_comment" => Comment { text: <> }, +} + // Also allows trailing separator #[inline] SepBy: Vec = { diff --git a/rust/candid_parser/src/token.rs b/rust/candid_parser/src/token.rs index db7ae2d61..22ca2e8c2 100644 --- a/rust/candid_parser/src/token.rs +++ b/rust/candid_parser/src/token.rs @@ -2,11 +2,13 @@ use lalrpop_util::ParseError; use logos::{Lexer, Logos}; #[derive(Logos, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)] -#[logos(skip r"[ \t\r\n]+")] -#[logos(skip r"//[^\n]*")] // line comment +#[logos(skip r"[ \t\r]+")] +#[logos(skip r"(//[^\n]*\n)+\n")] // ignore line comments that are followed by an empty line pub enum Token { #[token("/*")] StartComment, + #[regex(r"(//[^\n]*\n)+", parse_line_comment)] + LineComment(String), #[token("=")] Equals, #[token("(")] @@ -78,6 +80,8 @@ pub enum Token { Float(String), #[regex("true|false", |lex| lex.slice().parse().map_err(|_| ()))] Boolean(bool), + #[token("\n")] + Newline, } #[derive(Logos, Debug, Clone, PartialEq, Eq)] @@ -118,6 +122,15 @@ fn parse_number(lex: &mut Lexer) -> String { } } +fn parse_line_comment(lex: &mut Lexer) -> String { + lex.slice() + .lines() + // remove the leading "//" and trim any space/newline + .map(|s| s[2..].trim()) + .collect::>() + .join("\n") +} + pub struct Tokenizer<'input> { lex: Lexer<'input, Token>, } @@ -278,6 +291,7 @@ impl Iterator for Tokenizer<'_> { self.lex = lex.morph::(); Some(Ok((span.start, Token::Text(result), self.lex.span().end))) } + Ok(Token::Newline) => self.next(), Ok(token) => Some(Ok((span.start, token, span.end))), } } diff --git a/rust/candid_parser/src/types.rs b/rust/candid_parser/src/types.rs index feb470e24..0c2ead227 100644 --- a/rust/candid_parser/src/types.rs +++ b/rust/candid_parser/src/types.rs @@ -67,10 +67,16 @@ pub struct FuncType { pub rets: Vec, } +#[derive(Debug, Clone)] +pub struct Comment { + pub text: String, +} + #[derive(Debug, Clone)] pub struct TypeField { pub label: Label, pub typ: IDLType, + pub comment: Option, } #[derive(Debug)] @@ -84,6 +90,7 @@ pub enum Dec { pub struct Binding { pub id: String, pub typ: IDLType, + pub comment: Option, } #[derive(Debug)] diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 807f3690b..4ea756b25 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -16,10 +16,10 @@ pub fn ast_to_type(env: &TypeEnv, ast: &super::types::IDLType) -> Result { te: &mut env.clone(), pre: false, }; - check_type(&env, ast) + check_type(&env, ast, None) } -fn check_prim(prim: &PrimType) -> Type { +fn check_prim(prim: &PrimType) -> TypeInner { match prim { PrimType::Nat => TypeInner::Nat, PrimType::Nat8 => TypeInner::Nat8, @@ -39,41 +39,40 @@ fn check_prim(prim: &PrimType) -> Type { PrimType::Reserved => TypeInner::Reserved, PrimType::Empty => TypeInner::Empty, } - .into() } -pub fn check_type(env: &Env, t: &IDLType) -> Result { - match t { +pub fn check_type(env: &Env, t: &IDLType, comment: Option<&String>) -> Result { + let inner = match t { IDLType::PrimT(prim) => Ok(check_prim(prim)), IDLType::VarT(id) => { env.te.find_type(id)?; - Ok(TypeInner::Var(id.to_string()).into()) + Ok(TypeInner::Var(id.to_string())) } IDLType::OptT(t) => { - let t = check_type(env, t)?; - Ok(TypeInner::Opt(t).into()) + let t = check_type(env, t, comment)?; + Ok(TypeInner::Opt(t)) } IDLType::VecT(t) => { - let t = check_type(env, t)?; - Ok(TypeInner::Vec(t).into()) + let t = check_type(env, t, comment)?; + Ok(TypeInner::Vec(t)) } IDLType::RecordT(fs) => { let fs = check_fields(env, fs)?; - Ok(TypeInner::Record(fs).into()) + Ok(TypeInner::Record(fs)) } IDLType::VariantT(fs) => { let fs = check_fields(env, fs)?; - Ok(TypeInner::Variant(fs).into()) + Ok(TypeInner::Variant(fs)) } - IDLType::PrincipalT => Ok(TypeInner::Principal.into()), + IDLType::PrincipalT => Ok(TypeInner::Principal), IDLType::FuncT(func) => { let mut t1 = Vec::new(); for t in func.args.iter() { - t1.push(check_type(env, t)?); + t1.push(check_type(env, t, comment)?); } let mut t2 = Vec::new(); for t in func.rets.iter() { - t2.push(check_type(env, t)?); + t2.push(check_type(env, t, comment)?); } if func.modes.len() > 1 { return Err(Error::msg("cannot have more than one mode")); @@ -89,21 +88,22 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { args: t1, rets: t2, }; - Ok(TypeInner::Func(f).into()) + Ok(TypeInner::Func(f)) } IDLType::ServT(ms) => { let ms = check_meths(env, ms)?; - Ok(TypeInner::Service(ms).into()) + Ok(TypeInner::Service(ms)) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - } + }?; + Ok((inner, comment).into()) } fn check_fields(env: &Env, fs: &[TypeField]) -> Result> { // field label duplication is checked in the parser let mut res = Vec::new(); for f in fs.iter() { - let ty = check_type(env, &f.typ)?; + let ty = check_type(env, &f.typ, f.comment.as_ref().map(|c| &c.text))?; let field = Field { id: f.label.clone().into(), ty, @@ -117,7 +117,7 @@ fn check_meths(env: &Env, ms: &[Binding]) -> Result> { // binding duplication is checked in the parser let mut res = Vec::new(); for meth in ms.iter() { - let t = check_type(env, &meth.typ)?; + let t = check_type(env, &meth.typ, meth.comment.as_ref().map(|c| &c.text))?; if !env.pre && env.te.as_func(&t).is_err() { return Err(Error::msg(format!( "method {} is a non-function type", @@ -132,8 +132,8 @@ fn check_meths(env: &Env, ms: &[Binding]) -> Result> { fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { match dec { - Dec::TypD(Binding { id, typ }) => { - let t = check_type(env, typ)?; + Dec::TypD(Binding { id, typ, comment }) => { + let t = check_type(env, typ, comment.as_ref().map(|c| &c.text))?; env.te.0.insert(id.to_string(), t); } Dec::ImportType(_) | Dec::ImportServ(_) => (), @@ -167,7 +167,7 @@ fn check_cycle(env: &TypeEnv) -> Result<()> { fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { - if let Dec::TypD(Binding { id, typ: _ }) = dec { + if let Dec::TypD(Binding { id, .. }) = dec { let duplicate = env.te.0.insert(id.to_string(), TypeInner::Unknown.into()); if duplicate.is_some() { return Err(Error::msg(format!("duplicate binding for {id}"))); @@ -188,14 +188,14 @@ fn check_actor(env: &Env, actor: &Option) -> Result> { Some(IDLType::ClassT(ts, t)) => { let mut args = Vec::new(); for arg in ts.iter() { - args.push(check_type(env, arg)?); + args.push(check_type(env, arg, None)?); } - let serv = check_type(env, t)?; + let serv = check_type(env, t, None)?; env.te.as_service(&serv)?; Ok(Some(TypeInner::Class(args, serv).into())) } Some(typ) => { - let t = check_type(env, typ)?; + let t = check_type(env, typ, None)?; env.te.as_service(&t)?; Ok(Some(t)) } @@ -263,7 +263,7 @@ pub fn check_init_args( env.te.merge(main_env)?; let mut args = Vec::new(); for arg in prog.args.iter() { - args.push(check_type(&env, arg)?); + args.push(check_type(&env, arg, None)?); } Ok(args) } diff --git a/rust/candid_parser/tests/assets/comment.did b/rust/candid_parser/tests/assets/comment.did index e412fe385..1d6d4bf87 100644 --- a/rust/candid_parser/tests/assets/comment.did +++ b/rust/candid_parser/tests/assets/comment.did @@ -1,4 +1,10 @@ -// line comment +// ignored comment above import +import "import/a.did"; +// ignored line comment + +// another ignored line comment +// that spans multiple lines + /* gjkf jgfkg */ diff --git a/rust/candid_parser/tests/assets/management.did b/rust/candid_parser/tests/assets/management.did index 04f425222..4206bee26 100644 --- a/rust/candid_parser/tests/assets/management.did +++ b/rust/candid_parser/tests/assets/management.did @@ -1,3 +1,5 @@ +// This is a comment +// with mutilple lines type canister_id = principal; type user_id = principal; type wasm_module = blob; @@ -9,13 +11,17 @@ type canister_settings = record { freezing_threshold : opt nat; }; +// This is a second comment type definite_canister_settings = record { controllers : vec principal; compute_allocation : nat; + // This is a field comment memory_allocation : nat; freezing_threshold : nat; }; +// This is an ignored comment + type http_header = record { name: text; value: text }; type http_response = record { @@ -30,6 +36,7 @@ type satoshi = nat64; type bitcoin_network = variant { mainnet; + // This is a variant comment testnet; }; diff --git a/rust/candid_parser/tests/assets/ok/comment.d.ts b/rust/candid_parser/tests/assets/ok/comment.d.ts index 48942cde1..f4f98a043 100644 --- a/rust/candid_parser/tests/assets/ok/comment.d.ts +++ b/rust/candid_parser/tests/assets/ok/comment.d.ts @@ -2,5 +2,8 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +export type a = { 'a' : null } | + { 'b' : b }; +export type b = [bigint, bigint]; export type id = number; diff --git a/rust/candid_parser/tests/assets/ok/comment.did b/rust/candid_parser/tests/assets/ok/comment.did index 04bf164ff..8e1b4148e 100644 --- a/rust/candid_parser/tests/assets/ok/comment.did +++ b/rust/candid_parser/tests/assets/ok/comment.did @@ -1,2 +1,4 @@ +type a = variant { a; b : b }; +type b = record { int; nat }; type id = nat8; diff --git a/rust/candid_parser/tests/assets/ok/comment.js b/rust/candid_parser/tests/assets/ok/comment.js index 1f63d4782..45fe1ff2d 100644 --- a/rust/candid_parser/tests/assets/ok/comment.js +++ b/rust/candid_parser/tests/assets/ok/comment.js @@ -1,2 +1,5 @@ +const b = IDL.Rec(); +const a = IDL.Variant({ 'a' : IDL.Null, 'b' : b }); +b.fill(IDL.Tuple(IDL.Int, IDL.Nat)); const id = IDL.Nat8; diff --git a/rust/candid_parser/tests/assets/ok/comment.mo b/rust/candid_parser/tests/assets/ok/comment.mo index baf858e78..b03cc09f9 100644 --- a/rust/candid_parser/tests/assets/ok/comment.mo +++ b/rust/candid_parser/tests/assets/ok/comment.mo @@ -2,6 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type a = { #a; #b : b }; + public type b = (Int, Nat); public type id = Nat8; } diff --git a/rust/candid_parser/tests/assets/ok/comment.rs b/rust/candid_parser/tests/assets/ok/comment.rs index 958f42fac..dad80ce80 100644 --- a/rust/candid_parser/tests/assets/ok/comment.rs +++ b/rust/candid_parser/tests/assets/ok/comment.rs @@ -4,6 +4,10 @@ use candid::{self, CandidType, Deserialize, Principal}; use ic_cdk::api::call::CallResult as Result; +#[derive(CandidType, Deserialize)] +pub enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(Box) } +#[derive(CandidType, Deserialize)] +pub struct B (pub candid::Int,pub candid::Nat,); pub type Id = u8; diff --git a/rust/candid_parser/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did index 4af885072..85e31c1cb 100644 --- a/rust/candid_parser/tests/assets/ok/management.did +++ b/rust/candid_parser/tests/assets/ok/management.did @@ -1,6 +1,12 @@ type bitcoin_address = text; -type bitcoin_network = variant { mainnet; testnet }; +type bitcoin_network = variant { + mainnet; + // This is a variant comment + testnet; +}; type block_hash = blob; +// This is a comment +// with mutilple lines type canister_id = principal; type canister_settings = record { freezing_threshold : opt nat; @@ -8,9 +14,11 @@ type canister_settings = record { memory_allocation : opt nat; compute_allocation : opt nat; }; +// This is a second comment type definite_canister_settings = record { freezing_threshold : nat; controllers : vec principal; + // This is a field comment memory_allocation : nat; compute_allocation : nat; }; @@ -49,6 +57,7 @@ type user_id = principal; type utxo = record { height : nat32; value : satoshi; outpoint : outpoint }; type wasm_module = blob; service : { + // bitcoin interface bitcoin_get_balance : (get_balance_request) -> (satoshi); bitcoin_get_current_fee_percentiles : ( get_current_fee_percentiles_request, @@ -70,6 +79,7 @@ service : { ); delete_canister : (record { canister_id : canister_id }) -> (); deposit_cycles : (record { canister_id : canister_id }) -> (); + // Threshold ECDSA signature ecdsa_public_key : ( record { key_id : record { name : text; curve : ecdsa_curve }; @@ -100,6 +110,7 @@ service : { canister_id : canister_id; }, ) -> (); + // provisional interfaces for the pre-ledger world provisional_create_canister_with_cycles : ( record { settings : opt canister_settings; diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 0609c7387..981b215ad 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -3,8 +3,14 @@ module { public type bitcoin_address = Text; - public type bitcoin_network = { #mainnet; #testnet }; + public type bitcoin_network = { + #mainnet; + /// This is a variant comment + #testnet; + }; public type block_hash = Blob; + /// This is a comment + /// with mutilple lines public type canister_id = Principal; public type canister_settings = { freezing_threshold : ?Nat; @@ -12,6 +18,7 @@ module { memory_allocation : ?Nat; compute_allocation : ?Nat; }; + /// This is a second comment public type definite_canister_settings = { freezing_threshold : Nat; controllers : [Principal]; @@ -55,6 +62,7 @@ module { public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; public type wasm_module = Blob; public type Self = actor { + /// bitcoin interface bitcoin_get_balance : shared get_balance_request -> async satoshi; bitcoin_get_current_fee_percentiles : shared get_current_fee_percentiles_request -> async [ millisatoshi_per_byte @@ -74,6 +82,7 @@ module { }; delete_canister : shared { canister_id : canister_id } -> async (); deposit_cycles : shared { canister_id : canister_id } -> async (); + /// Threshold ECDSA signature ecdsa_public_key : shared { key_id : { name : Text; curve : ecdsa_curve }; canister_id : ?canister_id; @@ -99,6 +108,7 @@ module { mode : { #reinstall; #upgrade; #install }; canister_id : canister_id; } -> async (); + /// provisional interfaces for the pre-ledger world provisional_create_canister_with_cycles : shared { settings : ?canister_settings; specified_id : ?canister_id; diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index 311f8e1d6..2a0492c70 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -8,6 +8,7 @@ type Result = std::result::Result; pub enum BitcoinNetwork { #[serde(rename="mainnet")] Mainnet, + /// This is a variant comment #[serde(rename="testnet")] Testnet, } @@ -52,6 +53,8 @@ pub struct SendTransactionRequest { pub transaction: serde_bytes::ByteBuf, pub network: BitcoinNetwork, } +/// This is a comment +/// with mutilple lines pub type CanisterId = Principal; #[derive(CandidType, Deserialize)] pub struct CanisterStatusArg { pub canister_id: CanisterId } @@ -64,6 +67,7 @@ pub enum CanisterStatusRetStatus { #[serde(rename="running")] Running, } +/// This is a second comment #[derive(CandidType, Deserialize)] pub struct DefiniteCanisterSettings { pub freezing_threshold: candid::Nat, @@ -205,6 +209,7 @@ pub struct UpdateSettingsArg { pub struct Service<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> Service<'a> { + /// bitcoin interface pub async fn bitcoin_get_balance(&self, arg0: &GetBalanceRequest) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "bitcoin_get_balance").with_arg(args).call_and_wait().await?; @@ -245,6 +250,7 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "deposit_cycles").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } + /// Threshold ECDSA signature pub async fn ecdsa_public_key(&self, arg0: &EcdsaPublicKeyArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "ecdsa_public_key").with_arg(args).call_and_wait().await?; @@ -260,6 +266,7 @@ impl<'a> Service<'a> { let bytes = self.1.update(&self.0, "install_code").with_arg(args).call_and_wait().await?; Ok(Decode!(&bytes)?) } + /// provisional interfaces for the pre-ledger world pub async fn provisional_create_canister_with_cycles(&self, arg0: &ProvisionalCreateCanisterWithCyclesArg) -> Result { let args = Encode!(&arg0)?; let bytes = self.1.update(&self.0, "provisional_create_canister_with_cycles").with_arg(args).call_and_wait().await?; From 1c99b85db47d2a55b3b2ea91b2e3475546d5a8be Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 11 Jun 2025 20:51:53 +0200 Subject: [PATCH 02/23] feat: support comments in TS bindings --- rust/candid/src/pretty/utils.rs | 5 +-- rust/candid_parser/src/bindings/typescript.rs | 38 +++++++++++++++---- .../tests/assets/ok/management.d.ts | 26 ++++++++++++- 3 files changed, 58 insertions(+), 11 deletions(-) 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_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 5a4460bf2..dd45b3e0f 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -103,7 +103,8 @@ 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) + pp_comment(field.ty.1.as_ref()) + .append(pp_label(&field.id)) .append(kwd(":")) .append(pp_ty(env, &field.ty, is_ref)) } @@ -130,11 +131,14 @@ 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> { let doc = concat( serv.iter().map(|(id, func)| { - let func = match func.as_ref() { - TypeInner::Func(ref func) => pp_function(env, func), + let f = match func.as_ref() { + TypeInner::Func(ref inner) => pp_function(env, inner), _ => pp_ty(env, func, false), }; - quote_ident(id).append(kwd(":")).append(func) + pp_comment(func.1.as_ref()) + .append(quote_ident(id)) + .append(kwd(":")) + .append(f) }), ",", ); @@ -144,7 +148,7 @@ fn pp_service<'a>(env: &'a TypeEnv, serv: &'a [(String, Type)]) -> RcDoc<'a> { fn pp_defs<'a>(env: &'a TypeEnv, 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() { + let doc = match ty.as_ref() { TypeInner::Record(_) if !ty.is_tuple() => kwd("export interface") .append(ident(id)) .append(" ") @@ -156,7 +160,7 @@ fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { TypeInner::Func(ref func) => kwd("export type") .append(ident(id)) .append(" = ") - .append(pp_function(env, func)) + .append(pp_function(env, func)) // the comment is already added at the end of this match block .append(";"), _ => kwd("export type") .append(ident(id)) @@ -164,7 +168,7 @@ fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { .append(pp_ty(env, ty, false)) .append(";"), }; - export + pp_comment(ty.1.as_ref()).append(doc) })) } @@ -181,6 +185,26 @@ fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type) -> RcDoc<'a> { } } +fn pp_comment(comment: Option<&String>) -> RcDoc { + let mut comment_doc = RcDoc::nil(); + let mut is_empty = true; + if let Some(comment) = comment { + for line in comment.lines().filter(|l| !l.is_empty()) { + is_empty = false; + comment_doc = + comment_doc.append(RcDoc::text(" * ").append(line).append(RcDoc::hardline())); + } + } + if !is_empty { + comment_doc = RcDoc::text("/**") + .append(RcDoc::hardline()) + .append(comment_doc) + .append(RcDoc::text(" */")) + .append(RcDoc::hardline()); + } + comment_doc +} + pub fn compile(env: &TypeEnv, actor: &Option) -> String { let header = r#"import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index ca60e354d..34de8b92b 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -4,8 +4,17 @@ import type { IDL } from '@dfinity/candid'; export type bitcoin_address = string; export type bitcoin_network = { 'mainnet' : null } | - { 'testnet' : null }; + { + /** + * This is a variant comment + */ + 'testnet' : null + }; export type block_hash = Uint8Array | number[]; +/** + * This is a comment + * with mutilple lines + */ export type canister_id = Principal; export interface canister_settings { 'freezing_threshold' : [] | [bigint], @@ -13,9 +22,15 @@ export interface canister_settings { 'memory_allocation' : [] | [bigint], 'compute_allocation' : [] | [bigint], } +/** + * This is a second comment + */ export interface definite_canister_settings { 'freezing_threshold' : bigint, 'controllers' : Array, + /** + * This is a field comment + */ 'memory_allocation' : bigint, 'compute_allocation' : bigint, } @@ -63,6 +78,9 @@ export interface utxo { } export type wasm_module = Uint8Array | number[]; export interface _SERVICE { + /** + * bitcoin interface + */ 'bitcoin_get_balance' : ActorMethod<[get_balance_request], satoshi>, 'bitcoin_get_current_fee_percentiles' : ActorMethod< [get_current_fee_percentiles_request], @@ -92,6 +110,9 @@ export interface _SERVICE { >, 'delete_canister' : ActorMethod<[{ 'canister_id' : canister_id }], undefined>, 'deposit_cycles' : ActorMethod<[{ 'canister_id' : canister_id }], undefined>, + /** + * Threshold ECDSA signature + */ 'ecdsa_public_key' : ActorMethod< [ { @@ -138,6 +159,9 @@ export interface _SERVICE { ], undefined >, + /** + * provisional interfaces for the pre-ledger world + */ 'provisional_create_canister_with_cycles' : ActorMethod< [ { From f47cbb64e83750e836cb578d085d9353cd7a4ff4 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 11 Jun 2025 20:53:00 +0200 Subject: [PATCH 03/23] fix: typo --- rust/candid_parser/tests/assets/management.did | 2 +- rust/candid_parser/tests/assets/ok/management.d.ts | 2 +- rust/candid_parser/tests/assets/ok/management.did | 2 +- rust/candid_parser/tests/assets/ok/management.mo | 2 +- rust/candid_parser/tests/assets/ok/management.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/candid_parser/tests/assets/management.did b/rust/candid_parser/tests/assets/management.did index 4206bee26..3d0f85202 100644 --- a/rust/candid_parser/tests/assets/management.did +++ b/rust/candid_parser/tests/assets/management.did @@ -1,5 +1,5 @@ // This is a comment -// with mutilple lines +// with multiple lines type canister_id = principal; type user_id = principal; type wasm_module = blob; diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index 34de8b92b..51362de5f 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -13,7 +13,7 @@ export type bitcoin_network = { 'mainnet' : null } | export type block_hash = Uint8Array | number[]; /** * This is a comment - * with mutilple lines + * with multiple lines */ export type canister_id = Principal; export interface canister_settings { diff --git a/rust/candid_parser/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did index 85e31c1cb..5aa034ab2 100644 --- a/rust/candid_parser/tests/assets/ok/management.did +++ b/rust/candid_parser/tests/assets/ok/management.did @@ -6,7 +6,7 @@ type bitcoin_network = variant { }; type block_hash = blob; // This is a comment -// with mutilple lines +// with multiple lines type canister_id = principal; type canister_settings = record { freezing_threshold : opt nat; diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 981b215ad..723fa5f31 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -10,7 +10,7 @@ module { }; public type block_hash = Blob; /// This is a comment - /// with mutilple lines + /// with multiple lines public type canister_id = Principal; public type canister_settings = { freezing_threshold : ?Nat; diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index 2a0492c70..e62314679 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -54,7 +54,7 @@ pub struct SendTransactionRequest { pub network: BitcoinNetwork, } /// This is a comment -/// with mutilple lines +/// with multiple lines pub type CanisterId = Principal; #[derive(CandidType, Deserialize)] pub struct CanisterStatusArg { pub canister_id: CanisterId } From f439fe0489c6489903f4ddfef73e14bab967990e Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 11 Jun 2025 20:55:46 +0200 Subject: [PATCH 04/23] test: add comments to parse_idl_prog --- rust/candid_parser/tests/parse_type.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index ad9569404..cd2a2631d 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -12,21 +12,29 @@ use std::path::Path; fn parse_idl_prog() { let prog = r#" import "test.did"; +// This is a comment type my_type = principal; -type List = opt record { head: int; tail: List }; +type List = opt record { + head: int; + // This is a field comment + tail: List; +}; type f = func (List, func (int32) -> (int64)) -> (opt List); type broker = service { + // This is a method comment find : (name: text) -> (service {up:() -> (); current:() -> (nat32)}); }; type nested = record { nat; nat; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; }; +// Ignored comment + service server : { f : (test: blob, opt bool) -> () oneway; g : (my_type, List, opt List) -> (int) query; h : (vec opt text, variant { A: nat; B: opt text }, opt List) -> (record { id: nat; 0x2a: record {} }); i : f; -} +}; "#; prog.parse::().unwrap(); } From f5c2682e572c027cc7b8618044b61b06f2b5e19b Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 12 Jun 2025 09:39:04 +0200 Subject: [PATCH 05/23] feat: support comments in rust bindgen --- rust/candid/src/pretty/candid.rs | 12 +++---- rust/candid/src/types/internal.rs | 35 ++++++++++--------- rust/candid_parser/src/bindings/motoko.rs | 6 ++-- rust/candid_parser/src/bindings/rust.rs | 22 ++++++------ rust/candid_parser/src/bindings/typescript.rs | 6 ++-- .../tests/assets/ok/management.rs | 1 + 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 2dd1554f7..c2415e460 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(), ty.1.clone()) + pp_ty_inner(ty.as_ref(), ty.comment()) } -pub fn pp_ty_inner(ty: &TypeInner, comment: Option) -> RcDoc { +pub fn pp_ty_inner<'a>(ty: &'a TypeInner, comment: Option<&'a String>) -> RcDoc<'a> { use TypeInner::*; match ty { Null => str("null"), @@ -103,7 +103,7 @@ pub fn pp_ty_inner(ty: &TypeInner, comment: Option) -> 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(), comment); + 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,7 @@ pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { } else { kwd(" :").append(pp_ty(&field.ty)) }; - pp_comment(field.ty.1.as_ref()) + pp_comment(field.ty.comment()) .append(pp_label(&field.id)) .append(ty_doc) } @@ -197,7 +197,7 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { TypeInner::Var(_) => pp_ty(func), _ => unreachable!(), }; - pp_comment(func.1.as_ref()) + pp_comment(func.comment()) .append(pp_text(id)) .append(kwd(" :")) .append(func_doc) @@ -209,7 +209,7 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { fn pp_defs(env: &TypeEnv) -> RcDoc { lines(env.0.iter().map(|(id, ty)| { - pp_comment(ty.1.as_ref()) + pp_comment(ty.comment()) .append(kwd("type")) .append(ident(id)) .append(kwd("=")) diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 98a235cf5..77484144a 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -96,19 +96,19 @@ 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 inner = TypeInner::Record( fs.iter() .map(|Field { id, ty }| Field { id: id.clone(), ty: self.go(ty), }) .collect(), - ) - .into(); + ); + let res = (inner, t.comment()).into(); if t.is_tuple() { return res; } @@ -123,15 +123,15 @@ impl TypeContainer { } } TypeInner::Variant(fs) => { - let res: Type = TypeInner::Variant( + let inner = TypeInner::Variant( fs.iter() .map(|Field { id, ty }| Field { id: id.clone(), ty: self.go(ty), }) .collect(), - ) - .into(); + ); + let res = (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 +174,13 @@ 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, pub Option); +pub struct Type(pub std::rc::Rc, Option); #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] pub enum TypeInner { @@ -270,7 +270,7 @@ impl Type { } pub fn subst(&self, tau: &std::collections::BTreeMap) -> Self { use TypeInner::*; - match self.as_ref() { + let inner = match self.as_ref() { Var(id) => match tau.get(id) { None => Var(id.to_string()), Some(new_id) => Var(new_id.to_string()), @@ -323,8 +323,11 @@ impl Type { ty.subst(tau), ), _ => return self.clone(), - } - .into() + }; + (inner, self.comment()).into() + } + pub fn comment(&self) -> Option<&String> { + self.1.as_ref() } } #[cfg(feature = "printer")] @@ -665,7 +668,7 @@ pub fn is_primitive(t: &Type) -> bool { pub fn unroll(t: &Type) -> Type { use self::TypeInner::*; - match t.as_ref() { + let inner = match t.as_ref() { Knot(ref id) => return find_type(id).unwrap(), Opt(ref t) => Opt(unroll(t)), Vec(ref t) => Vec(unroll(t)), @@ -686,8 +689,8 @@ pub fn unroll(t: &Type) -> Type { .collect(), ), t => t.clone(), - } - .into() + }; + (inner, t.comment()).into() } thread_local! { diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 549059824..35aab5c4c 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -162,7 +162,7 @@ fn pp_field(field: &Field) -> RcDoc { pp_label(&field.id).append(" : ").append(pp_ty(&field.ty)) } fn pp_variant(field: &Field) -> RcDoc { - let doc = pp_comment(field.ty.1.as_ref()) + let doc = pp_comment(field.ty.comment()) .append(str("#")) .append(pp_label(&field.id)); if *field.ty != TypeInner::Null { @@ -243,7 +243,7 @@ fn pp_rets(args: &[Type]) -> RcDoc { fn pp_service(serv: &[(String, Type)]) -> RcDoc { let doc = concat( serv.iter().map(|(id, func)| { - pp_comment(func.1.as_ref()) + pp_comment(func.comment()) .append(escape(id, true)) .append(" : ") .append(pp_ty(func)) @@ -255,7 +255,7 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { fn pp_defs(env: &TypeEnv) -> RcDoc { lines(env.0.iter().map(|(id, ty)| { - pp_comment(ty.1.as_ref()) + pp_comment(ty.comment()) .append(kwd("public type")) .append(escape(id, false)) .append(" = ") diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index cf6092e9a..112bebbe2 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -357,7 +357,7 @@ fn test_{test_name}() {{ .pp_label(&field.id, false, need_vis) .append(kwd(":")) .append(self.pp_ty(&field.ty, is_ref)); - let res = pp_comment(field.ty.1.as_ref()).append(f); + let res = pp_comment(field.ty.comment()).append(f); self.state.pop_state(old, StateElem::Label(&lab)); res } @@ -397,7 +397,7 @@ fn test_{test_name}() {{ ")", )), }; - let res = pp_comment(field.ty.1.as_ref()).append(f); + let res = pp_comment(field.ty.comment()).append(f); self.state.pop_state(old, StateElem::Label(&lab)); res } @@ -500,7 +500,7 @@ fn test_{test_name}() {{ } } }; - let line_with_comment = pp_comment(ty.1.as_ref()).append(line); + let line_with_comment = pp_comment(ty.comment()).append(line); self.state.pop_state(old, StateElem::Label(id)); res.push(line_with_comment) } @@ -672,7 +672,7 @@ fn test_{test_name}() {{ let serv = self.state.env.as_service(&actor).unwrap(); let mut res = Vec::new(); for (id, func) in serv.iter() { - let comment = func.1.as_ref(); + let comment = func.comment(); let func = self.state.env.as_func(func).unwrap(); res.push(self.pp_function(id, func, comment)); } @@ -841,7 +841,7 @@ impl NominalState<'_> { } else { Some(self.state.push_state(&elem)) }; - let res = match t.as_ref() { + let inner = match t.as_ref() { TypeInner::Opt(ty) => { path.push(TypePath::Opt); let ty = self.nominalize(env, path, ty); @@ -885,7 +885,7 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Record(fs.to_vec()).into(), + &(TypeInner::Record(fs.to_vec()), None).into(), ); env.0.insert(new_var.clone(), ty); TypeInner::Var(new_var) @@ -923,7 +923,7 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Variant(fs.to_vec()).into(), + &(TypeInner::Variant(fs.to_vec()), None).into(), ); env.0.insert(new_var.clone(), ty); TypeInner::Var(new_var) @@ -988,7 +988,7 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Func(func.clone()).into(), + &(TypeInner::Func(func.clone()), None).into(), ); env.0.insert(new_var.clone(), ty); TypeInner::Var(new_var) @@ -1019,7 +1019,7 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Service(serv.clone()).into(), + &(TypeInner::Service(serv.clone()), None).into(), ); env.0.insert(new_var.clone(), ty); TypeInner::Var(new_var) @@ -1043,8 +1043,8 @@ impl NominalState<'_> { self.nominalize(env, path, ty), ), t => t.clone(), - } - .into(); + }; + let res = (inner, t.comment()).into(); if let Some(old) = old { self.state.pop_state(old, elem); } diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index dd45b3e0f..9c440f77a 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -103,7 +103,7 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } fn pp_field<'a>(env: &'a TypeEnv, field: &'a Field, is_ref: bool) -> RcDoc<'a> { - pp_comment(field.ty.1.as_ref()) + pp_comment(field.ty.comment()) .append(pp_label(&field.id)) .append(kwd(":")) .append(pp_ty(env, &field.ty, is_ref)) @@ -135,7 +135,7 @@ fn pp_service<'a>(env: &'a TypeEnv, serv: &'a [(String, Type)]) -> RcDoc<'a> { TypeInner::Func(ref inner) => pp_function(env, inner), _ => pp_ty(env, func, false), }; - pp_comment(func.1.as_ref()) + pp_comment(func.comment()) .append(quote_ident(id)) .append(kwd(":")) .append(f) @@ -168,7 +168,7 @@ fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { .append(pp_ty(env, ty, false)) .append(";"), }; - pp_comment(ty.1.as_ref()).append(doc) + pp_comment(ty.comment()).append(doc) })) } diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index e62314679..4162bfee3 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -72,6 +72,7 @@ pub enum CanisterStatusRetStatus { pub struct DefiniteCanisterSettings { pub freezing_threshold: candid::Nat, pub controllers: Vec, + /// This is a field comment pub memory_allocation: candid::Nat, pub compute_allocation: candid::Nat, } From 008342773213a7bad2e70ca67a50d9cc3bdee085 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 12 Jun 2025 10:36:23 +0200 Subject: [PATCH 06/23] feat: support line comments for actors --- rust/candid/src/pretty/candid.rs | 13 +++-- rust/candid_parser/src/bindings/motoko.rs | 4 +- rust/candid_parser/src/bindings/rust.rs | 50 ++++++++++++------- .../candid_parser/src/bindings/rust_agent.hbs | 6 ++- rust/candid_parser/src/bindings/rust_call.hbs | 9 +++- rust/candid_parser/src/bindings/typescript.rs | 3 +- rust/candid_parser/src/grammar.lalrpop | 8 +-- rust/candid_parser/src/types.rs | 8 ++- rust/candid_parser/src/typing.rs | 36 +++++++------ rust/candid_parser/tests/assets/actor.did | 1 + .../candid_parser/tests/assets/management.did | 1 + rust/candid_parser/tests/assets/ok/actor.d.ts | 3 ++ rust/candid_parser/tests/assets/ok/actor.did | 1 + rust/candid_parser/tests/assets/ok/actor.mo | 1 + rust/candid_parser/tests/assets/ok/actor.rs | 5 +- rust/candid_parser/tests/assets/ok/cyclic.rs | 3 +- rust/candid_parser/tests/assets/ok/empty.rs | 3 +- rust/candid_parser/tests/assets/ok/escape.rs | 3 +- rust/candid_parser/tests/assets/ok/example.rs | 3 +- .../candid_parser/tests/assets/ok/fieldnat.rs | 3 +- rust/candid_parser/tests/assets/ok/keyword.rs | 3 +- .../tests/assets/ok/management.d.ts | 3 ++ .../tests/assets/ok/management.did | 1 + .../tests/assets/ok/management.mo | 1 + .../tests/assets/ok/management.rs | 4 +- .../tests/assets/ok/recursion.rs | 3 +- .../tests/assets/ok/recursive_class.rs | 3 +- rust/candid_parser/tests/assets/ok/service.rs | 3 +- rust/candid_parser/tests/assets/ok/unicode.rs | 3 +- rust/candid_parser/tests/parse_type.rs | 1 + 30 files changed, 133 insertions(+), 56 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index c2415e460..077b7721c 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -113,9 +113,14 @@ pub fn pp_ty_inner<'a>(ty: &'a TypeInner, comment: Option<&'a String>) -> RcDoc< } 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)), + Service(ref serv) => pp_comment(comment) + .append(kwd("service")) + .append(pp_service(serv)), Class(ref args, ref t) => { - let doc = pp_args(args).append(" ->").append(RcDoc::space()); + let doc = pp_comment(comment) + .append(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), @@ -244,7 +249,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_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 35aab5c4c..7a19a823b 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -291,7 +291,9 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { None => pp_defs(env), Some(actor) => { let defs = pp_defs(env); - let actor = kwd("public type Self =").append(pp_actor(actor)); + let actor = pp_comment(actor.comment()) + .append(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 112bebbe2..f5fc90ae2 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -11,6 +11,8 @@ use serde::Serialize; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; +const COMMENT_PREFIX: &str = "/// "; + #[derive(Default, Deserialize, Clone, Debug)] pub struct BindingConfig { name: Option, @@ -156,12 +158,23 @@ fn pp_comment(comment: Option<&String>) -> RcDoc { let mut comment_doc = RcDoc::nil(); if let Some(comment) = comment { for line in comment.lines() { - comment_doc = - comment_doc.append(RcDoc::text("/// ").append(line).append(RcDoc::line())); + comment_doc = comment_doc.append( + RcDoc::text(COMMENT_PREFIX) + .append(line) + .append(RcDoc::line()), + ); } } comment_doc } +fn map_comment(comment: Option<&String>) -> Option { + comment.map(|c| { + c.lines() + .map(|l| format!("{COMMENT_PREFIX}{l}")) + .collect::>() + .join("\n") + }) +} impl<'a> State<'a> { fn generate_test(&mut self, src: &Type, use_type: &str) { @@ -632,17 +645,12 @@ fn test_{test_name}() {{ .map(|x| x.pretty(LINE_WIDTH).to_string()) .collect(), mode, - comment: comment.map(|c| { - c.lines() - .map(|l| format!("/// {l}")) - .collect::>() - .join("\n") - }), + comment: map_comment(comment), }; self.state.pop_state(old, StateElem::Label(id)); res } - fn pp_actor(&mut self, actor: &Type) -> (Vec, Option>) { + fn pp_actor(&mut self, actor: &Type) -> (Methods, InitArgs, Option) { let actor = self.state.env.trace_type(actor).unwrap(); let init = if let TypeInner::Class(args, _) = actor.as_ref() { let old = self.state.push_state(&StateElem::Label("init")); @@ -676,25 +684,30 @@ fn test_{test_name}() {{ let func = self.state.env.as_func(func).unwrap(); res.push(self.pp_function(id, func, comment)); } - (res, init) + (res, init, map_comment(actor.comment())) } } #[derive(Serialize, Debug)] pub struct Output { pub type_defs: String, - pub methods: Vec, - pub init_args: Option>, + pub methods: Methods, + pub init_args: InitArgs, + pub actor_comment: Option, pub tests: String, } #[derive(Serialize, Debug)] pub struct Method { pub name: String, pub original_name: String, - pub args: Vec<(String, String)>, + pub args: Args, pub rets: Vec, pub mode: String, pub comment: Option, } +type Methods = Vec; +type Args = Vec<(String, String)>; +type InitArgs = Option; + pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Output, Vec) { let mut state = NominalState { state: crate::configs::State::new(&tree.0, env), @@ -714,10 +727,10 @@ 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, actor_comment) = if let Some(actor) = &actor { state.pp_actor(actor) } else { - (Vec::new(), None) + (Vec::new(), None, None) }; let tests = state.tests.into_values().collect::>().join("\n"); let unused = state.state.report_unused(); @@ -726,6 +739,7 @@ pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Outp type_defs: defs.pretty(LINE_WIDTH).to_string(), methods, init_args, + actor_comment, tests, }, unused, @@ -738,8 +752,9 @@ pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) #[serde(flatten)] external: BTreeMap, type_defs: String, - methods: Vec, - init_args: Option>, + methods: Methods, + init_args: InitArgs, + actor_comment: Option, tests: String, } let data = HBOutput { @@ -747,6 +762,7 @@ pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) methods: output.methods, external: config.0, init_args: output.init_args, + actor_comment: output.actor_comment, tests: output.tests, }; hbs.render_template(template, &data).unwrap() diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs index 815e895bb..83da7cb57 100644 --- a/rust/candid_parser/src/bindings/rust_agent.hbs +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -6,6 +6,9 @@ type Result = std::result::Result; {{type_defs}} {{#if methods}} +{{#if actor_comment}} +{{actor_comment}} +{{/if}} pub struct {{PascalCase service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> {{PascalCase service_name}}<'a> { {{#each methods}} @@ -20,7 +23,8 @@ impl<'a> {{PascalCase service_name}}<'a> { {{/each}} } {{#if canister_id}} -pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} +/// Canister ID: `{{canister_id}}` +pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); {{/if}} {{/if}} {{#if tests}} diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs index 80096bcf2..83d11db94 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -6,6 +6,9 @@ use ic_cdk::api::call::CallResult as Result; {{type_defs}} {{#if methods}} +{{#if actor_comment}} +{{actor_comment}} +{{/if}} pub struct {{PascalCase service_name}}(pub Principal); impl {{PascalCase service_name}} { {{#each methods}} @@ -18,7 +21,11 @@ impl {{PascalCase service_name}} { {{/each}} } {{#if canister_id}} -pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); // {{canister_id}} +/// Canister ID: `{{canister_id}}` +pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); +{{#if actor_comment}} +{{actor_comment}} +{{/if}} pub const {{snake_case service_name}} : {{PascalCase service_name}} = {{PascalCase service_name}}(CANISTER_ID); {{/if}} {{/if}} diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 9c440f77a..020b486fb 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -214,7 +214,8 @@ import type { IDL } from '@dfinity/candid'; let defs = pp_defs(env, &def_list); let actor = match actor { None => RcDoc::nil(), - Some(actor) => pp_actor(env, actor) + Some(actor) => pp_comment(actor.comment()) + .append(pp_actor(env, actor)) .append(RcDoc::line()) .append("export declare const idlFactory: IDL.InterfaceFactory;") .append(RcDoc::line()) diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index df3b4fd38..b767ef3a0 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -1,4 +1,4 @@ -use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType, Comment}; +use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, ActorBinding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType, Comment}; use super::test::{Assert, Input, Test}; use super::token::{Token, error, error2, LexicalError, Span}; use candid::{Principal, types::Label}; @@ -271,9 +271,9 @@ Actor: IDLType = { "id" => IDLType::VarT(<>), } -MainActor: IDLType = { - "service" "id"? ":" ";"? => <>, - "service" "id"? ":" "->" ";"? => IDLType::ClassT(args, Box::new(t)), +MainActor: ActorBinding = { + "service" "id"? ":" ";"? => ActorBinding { typ: t, comment }, + "service" "id"? ":" "->" ";"? => ActorBinding { typ: IDLType::ClassT(args, Box::new(t)), comment }, } pub IDLProg: IDLProg = { diff --git a/rust/candid_parser/src/types.rs b/rust/candid_parser/src/types.rs index 304f2a9ea..ae2c36ef3 100644 --- a/rust/candid_parser/src/types.rs +++ b/rust/candid_parser/src/types.rs @@ -117,10 +117,16 @@ pub struct Binding { pub comment: Option, } +#[derive(Debug, Clone)] +pub struct ActorBinding { + pub typ: IDLType, + pub comment: Option, +} + #[derive(Debug)] pub struct IDLProg { pub decs: Vec, - pub actor: Option, + pub actor: Option, } #[derive(Debug)] diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 688d2866d..d8f801660 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -189,23 +189,29 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { Ok(()) } -fn check_actor(env: &Env, actor: &Option) -> Result> { - match actor { - None => Ok(None), - Some(IDLType::ClassT(ts, t)) => { - let mut args = Vec::new(); - for arg in ts.iter() { - args.push(check_arg(env, arg)?); +fn check_actor(env: &Env, actor: &Option) -> Result> { + if let Some(ActorBinding { typ, comment }) = actor { + let comment = comment.as_ref().map(|c| c.text.clone()); + match typ { + IDLType::ClassT(ts, t) => { + let mut args = Vec::new(); + for arg in ts.iter() { + args.push(check_arg(env, arg)?); + } + let serv = check_type(env, t, None)?; + env.te.as_service(&serv)?; + Ok(Some( + (TypeInner::Class(args, serv), comment.as_ref()).into(), + )) + } + _ => { + let t = check_type(env, typ, comment.as_ref())?; + env.te.as_service(&t)?; + Ok(Some(t)) } - let serv = check_type(env, t, None)?; - env.te.as_service(&serv)?; - Ok(Some(TypeInner::Class(args, serv).into())) - } - Some(typ) => { - let t = check_type(env, typ, None)?; - env.te.as_service(&t)?; - Ok(Some(t)) } + } else { + Ok(None) } } diff --git a/rust/candid_parser/tests/assets/actor.did b/rust/candid_parser/tests/assets/actor.did index 12a7e20d4..8d392d726 100644 --- a/rust/candid_parser/tests/assets/actor.did +++ b/rust/candid_parser/tests/assets/actor.did @@ -2,6 +2,7 @@ type f = func (int8) -> (int8); type g = f; type h = func (f) -> (f); type o = opt o; +// This is a service comment service : { f : (nat) -> (h); diff --git a/rust/candid_parser/tests/assets/management.did b/rust/candid_parser/tests/assets/management.did index 3d0f85202..24965a650 100644 --- a/rust/candid_parser/tests/assets/management.did +++ b/rust/candid_parser/tests/assets/management.did @@ -88,6 +88,7 @@ type send_transaction_request = record { type millisatoshi_per_byte = nat64; +// This is a service comment service ic : { create_canister : (record { settings : opt canister_settings diff --git a/rust/candid_parser/tests/assets/ok/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts index 7e88ad9b3..38873aef7 100644 --- a/rust/candid_parser/tests/assets/ok/actor.d.ts +++ b/rust/candid_parser/tests/assets/ok/actor.d.ts @@ -6,6 +6,9 @@ export type f = ActorMethod<[number], number>; export type g = f; export type h = ActorMethod<[[Principal, string]], [Principal, string]>; export type o = [] | [o]; +/** + * This is a service comment + */ export interface _SERVICE { 'f' : ActorMethod<[bigint], [Principal, string]>, 'g' : f, diff --git a/rust/candid_parser/tests/assets/ok/actor.did b/rust/candid_parser/tests/assets/ok/actor.did index b9616ec5b..2be70b17c 100644 --- a/rust/candid_parser/tests/assets/ok/actor.did +++ b/rust/candid_parser/tests/assets/ok/actor.did @@ -2,4 +2,5 @@ type f = func (int8) -> (int8); type g = f; type h = func (f) -> (f); type o = opt o; +// This is a service comment service : { f : (nat) -> (h); g : f; h : g; o : (o) -> (o) } diff --git a/rust/candid_parser/tests/assets/ok/actor.mo b/rust/candid_parser/tests/assets/ok/actor.mo index 9de5b8b1a..cb7318c5f 100644 --- a/rust/candid_parser/tests/assets/ok/actor.mo +++ b/rust/candid_parser/tests/assets/ok/actor.mo @@ -6,6 +6,7 @@ module { public type g = f; public type h = shared f -> async f; public type o = ?o; + /// This is a service comment public type Self = actor { f : shared Nat -> async h; g : f; diff --git a/rust/candid_parser/tests/assets/ok/actor.rs b/rust/candid_parser/tests/assets/ok/actor.rs index 2b30a3050..3434fc54a 100644 --- a/rust/candid_parser/tests/assets/ok/actor.rs +++ b/rust/candid_parser/tests/assets/ok/actor.rs @@ -10,6 +10,7 @@ pub type G = F; #[derive(CandidType, Deserialize)] pub struct O(pub Option>); +/// This is a service comment pub struct Service(pub Principal); impl Service { pub async fn f(&self, arg0: &candid::Nat) -> Result<(H,)> { @@ -25,6 +26,8 @@ impl Service { ic_cdk::call(self.0, "o", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); +/// This is a service comment pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/cyclic.rs b/rust/candid_parser/tests/assets/ok/cyclic.rs index 33eaa02fe..cb12343cc 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.rs +++ b/rust/candid_parser/tests/assets/ok/cyclic.rs @@ -18,6 +18,7 @@ impl Service { ic_cdk::call(self.0, "f", (arg0,arg1,arg2,arg3,arg4,arg5,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/empty.rs b/rust/candid_parser/tests/assets/ok/empty.rs index 4477e9f99..899477cc8 100644 --- a/rust/candid_parser/tests/assets/ok/empty.rs +++ b/rust/candid_parser/tests/assets/ok/empty.rs @@ -27,6 +27,7 @@ impl Service { ic_cdk::call(self.0, "h", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/escape.rs b/rust/candid_parser/tests/assets/ok/escape.rs index 0fde3e5d9..96feb0727 100644 --- a/rust/candid_parser/tests/assets/ok/escape.rs +++ b/rust/candid_parser/tests/assets/ok/escape.rs @@ -22,6 +22,7 @@ impl Service { ic_cdk::call(self.0, "\n\'\"\'\'\"\"\r\t", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index c4a9d4002..fcb69b857 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -129,7 +129,8 @@ impl Service { ic_cdk::call(self.0, "x", (arg0,arg1,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); #[test] fn test_Arc_MyList_() { diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.rs b/rust/candid_parser/tests/assets/ok/fieldnat.rs index 3d335e76b..43c063fd1 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.rs +++ b/rust/candid_parser/tests/assets/ok/fieldnat.rs @@ -51,6 +51,7 @@ impl Service { ic_cdk::call(self.0, "foo", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/keyword.rs b/rust/candid_parser/tests/assets/ok/keyword.rs index e0b8ddb7b..d25756994 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.rs +++ b/rust/candid_parser/tests/assets/ok/keyword.rs @@ -76,6 +76,7 @@ impl Service { ic_cdk::call(self.0, "variant", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index 51362de5f..fc8ef4530 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -77,6 +77,9 @@ export interface utxo { 'outpoint' : outpoint, } export type wasm_module = Uint8Array | number[]; +/** + * This is a service comment + */ export interface _SERVICE { /** * bitcoin interface diff --git a/rust/candid_parser/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did index 5aa034ab2..5dfe89c81 100644 --- a/rust/candid_parser/tests/assets/ok/management.did +++ b/rust/candid_parser/tests/assets/ok/management.did @@ -56,6 +56,7 @@ type send_transaction_request = record { type user_id = principal; type utxo = record { height : nat32; value : satoshi; outpoint : outpoint }; type wasm_module = blob; +// This is a service comment service : { // bitcoin interface bitcoin_get_balance : (get_balance_request) -> (satoshi); diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 723fa5f31..7df60f0ca 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -61,6 +61,7 @@ module { public type user_id = Principal; public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; public type wasm_module = Blob; + /// This is a service comment public type Self = actor { /// bitcoin interface bitcoin_get_balance : shared get_balance_request -> async satoshi; diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index 4162bfee3..d8bbd575a 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -208,6 +208,7 @@ pub struct UpdateSettingsArg { pub settings: CanisterSettings, } +/// This is a service comment pub struct Service<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> Service<'a> { /// bitcoin interface @@ -309,5 +310,6 @@ impl<'a> Service<'a> { Ok(Decode!(&bytes)?) } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); diff --git a/rust/candid_parser/tests/assets/ok/recursion.rs b/rust/candid_parser/tests/assets/ok/recursion.rs index 3eb52d95f..239310042 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.rs +++ b/rust/candid_parser/tests/assets/ok/recursion.rs @@ -38,6 +38,7 @@ impl Service { ic_cdk::call(self.0, "g", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.rs b/rust/candid_parser/tests/assets/ok/recursive_class.rs index ce01cd0ea..6678e8ca0 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.rs +++ b/rust/candid_parser/tests/assets/ok/recursive_class.rs @@ -12,6 +12,7 @@ impl Service { ic_cdk::call(self.0, "next", ()).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/service.rs b/rust/candid_parser/tests/assets/ok/service.rs index 631c2095a..cbcf60cd7 100644 --- a/rust/candid_parser/tests/assets/ok/service.rs +++ b/rust/candid_parser/tests/assets/ok/service.rs @@ -30,6 +30,7 @@ impl Service { ic_cdk::call(self.0, "asVariant", ()).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/assets/ok/unicode.rs b/rust/candid_parser/tests/assets/ok/unicode.rs index 87c596f46..b5bfbb954 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.rs +++ b/rust/candid_parser/tests/assets/ok/unicode.rs @@ -42,6 +42,7 @@ impl Service { ic_cdk::call(self.0, "👀", (arg0,)).await } } -pub const CANISTER_ID : Principal = Principal::from_slice(&[]); // aaaaa-aa +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); pub const service : Service = Service(CANISTER_ID); diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index cd2a2631d..dfaf97d39 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -29,6 +29,7 @@ type nested = record { nat; nat; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat // Ignored comment +// This is a service comment service server : { f : (test: blob, opt bool) -> () oneway; g : (my_type, List, opt List) -> (int) query; From 9823791d05cd60a5f47cb0518f2aa61fc32020cc Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 12 Jun 2025 11:16:13 +0200 Subject: [PATCH 07/23] fix: do not prepend comments in inner types --- rust/candid/src/pretty/candid.rs | 9 ++------- rust/candid_parser/src/bindings/rust.rs | 6 +++--- rust/candid_parser/src/grammar.lalrpop | 2 +- rust/candid_parser/tests/assets/class.did | 2 ++ rust/candid_parser/tests/assets/ok/class.d.ts | 6 ++++++ rust/candid_parser/tests/assets/ok/class.did | 2 ++ rust/candid_parser/tests/assets/ok/class.mo | 2 ++ rust/candid_parser/tests/assets/ok/class.rs | 9 +++++++-- 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 077b7721c..30ef4eaa1 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -113,14 +113,9 @@ pub fn pp_ty_inner<'a>(ty: &'a TypeInner, comment: Option<&'a String>) -> RcDoc< } Variant(ref fs) => kwd("variant").append(pp_fields(fs, true)), Func(ref func) => kwd("func").append(pp_function(func)), - Service(ref serv) => pp_comment(comment) - .append(kwd("service")) - .append(pp_service(serv)), + Service(ref serv) => kwd("service").append(pp_service(serv)), Class(ref args, ref t) => { - let doc = pp_comment(comment) - .append(pp_args(args)) - .append(" ->") - .append(RcDoc::space()); + 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), diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index f5fc90ae2..6db367252 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -11,7 +11,7 @@ use serde::Serialize; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; -const COMMENT_PREFIX: &str = "/// "; +const DOC_COMMENT_LINE_PREFIX: &str = "/// "; #[derive(Default, Deserialize, Clone, Debug)] pub struct BindingConfig { @@ -159,7 +159,7 @@ fn pp_comment(comment: Option<&String>) -> RcDoc { if let Some(comment) = comment { for line in comment.lines() { comment_doc = comment_doc.append( - RcDoc::text(COMMENT_PREFIX) + RcDoc::text(DOC_COMMENT_LINE_PREFIX) .append(line) .append(RcDoc::line()), ); @@ -170,7 +170,7 @@ fn pp_comment(comment: Option<&String>) -> RcDoc { fn map_comment(comment: Option<&String>) -> Option { comment.map(|c| { c.lines() - .map(|l| format!("{COMMENT_PREFIX}{l}")) + .map(|l| format!("{DOC_COMMENT_LINE_PREFIX}{l}")) .collect::>() .join("\n") }) diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index b767ef3a0..97bfc956e 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -272,7 +272,7 @@ Actor: IDLType = { } MainActor: ActorBinding = { - "service" "id"? ":" ";"? => ActorBinding { typ: t, comment }, + "service" "id"? ":" ";"? => ActorBinding { typ, comment }, "service" "id"? ":" "->" ";"? => ActorBinding { typ: IDLType::ClassT(args, Box::new(t)), comment }, } diff --git a/rust/candid_parser/tests/assets/class.did b/rust/candid_parser/tests/assets/class.did index fb1fdfb7f..8b0899b57 100644 --- a/rust/candid_parser/tests/assets/class.did +++ b/rust/candid_parser/tests/assets/class.did @@ -1,6 +1,8 @@ type Profile = record { age: nat8; name: text }; type List = opt record { int; List }; +// This is a service comment service : (int, l : List, Profile) -> { + // This is a method comment get : () -> (List); set : (List) -> (List); } diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index 412ee8514..76337a64d 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -4,7 +4,13 @@ import type { IDL } from '@dfinity/candid'; export type List = [] | [[bigint, List]]; export interface Profile { 'age' : number, 'name' : string } +/** + * This is a service comment + */ export interface _SERVICE { + /** + * This is a method comment + */ '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..8bbf7bb84 100644 --- a/rust/candid_parser/tests/assets/ok/class.did +++ b/rust/candid_parser/tests/assets/ok/class.did @@ -1,6 +1,8 @@ type List = opt record { int; List }; type Profile = record { age : nat8; name : text }; +// This is a service comment service : (int, l : List, Profile) -> { + // This is a method comment 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..88d3c9e95 100644 --- a/rust/candid_parser/tests/assets/ok/class.mo +++ b/rust/candid_parser/tests/assets/ok/class.mo @@ -4,7 +4,9 @@ module { public type List = ?(Int, List); public type Profile = { age : Nat8; name : Text }; + /// This is a service comment public type Self = (Int, l : List, Profile) -> async actor { + /// This is a method comment get : shared () -> async List; set : shared List -> async List; } diff --git a/rust/candid_parser/tests/assets/ok/class.rs b/rust/candid_parser/tests/assets/ok/class.rs index be0429b4a..f92f57070 100644 --- a/rust/candid_parser/tests/assets/ok/class.rs +++ b/rust/candid_parser/tests/assets/ok/class.rs @@ -12,6 +12,7 @@ pub struct Profile { pub age: u8, pub name: String } fn init(arg0: candid::Int, l: List, arg2: Profile) { unimplemented!() } +/// This is a method comment #[ic_cdk::update] fn get() -> List { unimplemented!() @@ -21,6 +22,10 @@ fn set(arg0: List) -> List { unimplemented!() } #[link_section = "icp:public candid:service"] -pub static __SERVICE: [u8; 94] = *br#"type List = opt record { int; List }; -service : { get : () -> (List); set : (List) -> (List) }"#; +pub static __SERVICE: [u8; 129] = *br#"type List = opt record { int; List }; +service : { + // This is a method comment + get : () -> (List); + set : (List) -> (List); +}"#; From ee0222d7d894d04364f0f03c20551efc4171c745 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 12 Jun 2025 13:38:10 +0200 Subject: [PATCH 08/23] fix: support multi-line comments, even nested --- rust/candid/src/pretty/candid.rs | 3 +- rust/candid_parser/src/bindings/motoko.rs | 2 +- rust/candid_parser/src/bindings/rust.rs | 36 +++++++------- .../candid_parser/src/bindings/rust_agent.hbs | 12 ++--- rust/candid_parser/src/bindings/rust_call.hbs | 18 +++---- rust/candid_parser/src/bindings/rust_stub.hbs | 6 +-- rust/candid_parser/src/token.rs | 5 +- rust/candid_parser/src/typing.rs | 2 +- rust/candid_parser/tests/assets/example.did | 29 +++++++++++- rust/candid_parser/tests/assets/import/a.did | 1 + .../candid_parser/tests/assets/import/b/b.did | 2 + .../candid_parser/tests/assets/management.did | 4 +- .../tests/assets/ok/example.d.ts | 47 +++++++++++++++++-- .../candid_parser/tests/assets/ok/example.did | 17 ++++++- rust/candid_parser/tests/assets/ok/example.mo | 11 +++++ rust/candid_parser/tests/assets/ok/example.rs | 17 ++++++- .../tests/assets/ok/management.d.ts | 10 +++- .../tests/assets/ok/management.did | 4 +- .../tests/assets/ok/management.mo | 4 +- .../tests/assets/ok/management.rs | 4 +- 20 files changed, 180 insertions(+), 54 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 30ef4eaa1..ca770a776 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -230,7 +230,8 @@ fn pp_comment(comment: Option<&String>) -> RcDoc { let mut comment_doc = RcDoc::nil(); if let Some(comment) = comment { for line in comment.lines() { - comment_doc = comment_doc.append(RcDoc::text("// ").append(line).append(RcDoc::line())); + comment_doc = + comment_doc.append(RcDoc::text("// ").append(line).append(RcDoc::hardline())); } } comment_doc diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 7a19a823b..cda7eda85 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -277,7 +277,7 @@ fn pp_comment(comment: Option<&String>) -> RcDoc { if let Some(comment) = comment { for line in comment.lines() { comment_doc = - comment_doc.append(RcDoc::text("/// ").append(line).append(RcDoc::line())); + comment_doc.append(RcDoc::text("/// ").append(line).append(RcDoc::hardline())); } } comment_doc diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 6db367252..69c9d9cf5 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -161,19 +161,20 @@ fn pp_comment(comment: Option<&String>) -> RcDoc { comment_doc = comment_doc.append( RcDoc::text(DOC_COMMENT_LINE_PREFIX) .append(line) - .append(RcDoc::line()), + .append(RcDoc::hardline()), ); } } comment_doc } -fn map_comment(comment: Option<&String>) -> Option { - comment.map(|c| { - c.lines() - .map(|l| format!("{DOC_COMMENT_LINE_PREFIX}{l}")) - .collect::>() - .join("\n") - }) +fn map_comment(comment: Option<&String>) -> CommentLines { + comment + .map(|c| { + c.lines() + .map(|l| format!("{DOC_COMMENT_LINE_PREFIX}{l}")) + .collect() + }) + .unwrap_or_default() } impl<'a> State<'a> { @@ -645,12 +646,12 @@ fn test_{test_name}() {{ .map(|x| x.pretty(LINE_WIDTH).to_string()) .collect(), mode, - comment: map_comment(comment), + comment_lines: map_comment(comment), }; self.state.pop_state(old, StateElem::Label(id)); res } - fn pp_actor(&mut self, actor: &Type) -> (Methods, InitArgs, Option) { + fn pp_actor(&mut self, actor: &Type) -> (Methods, InitArgs, CommentLines) { let actor = self.state.env.trace_type(actor).unwrap(); let init = if let TypeInner::Class(args, _) = actor.as_ref() { let old = self.state.push_state(&StateElem::Label("init")); @@ -692,7 +693,7 @@ pub struct Output { pub type_defs: String, pub methods: Methods, pub init_args: InitArgs, - pub actor_comment: Option, + pub actor_comment_lines: CommentLines, pub tests: String, } #[derive(Serialize, Debug)] @@ -702,11 +703,12 @@ pub struct Method { pub args: Args, pub rets: Vec, pub mode: String, - pub comment: Option, + pub comment_lines: CommentLines, } type Methods = Vec; type Args = Vec<(String, String)>; type InitArgs = Option; +type CommentLines = Vec; pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Output, Vec) { let mut state = NominalState { @@ -727,10 +729,10 @@ 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, actor_comment) = if let Some(actor) = &actor { + let (methods, init_args, actor_comment_lines) = if let Some(actor) = &actor { state.pp_actor(actor) } else { - (Vec::new(), None, None) + (Vec::new(), None, Vec::new()) }; let tests = state.tests.into_values().collect::>().join("\n"); let unused = state.state.report_unused(); @@ -739,7 +741,7 @@ pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Outp type_defs: defs.pretty(LINE_WIDTH).to_string(), methods, init_args, - actor_comment, + actor_comment_lines, tests, }, unused, @@ -754,7 +756,7 @@ pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) type_defs: String, methods: Methods, init_args: InitArgs, - actor_comment: Option, + actor_comment_lines: CommentLines, tests: String, } let data = HBOutput { @@ -762,7 +764,7 @@ pub fn output_handlebar(output: Output, config: ExternalConfig, template: &str) methods: output.methods, external: config.0, init_args: output.init_args, - actor_comment: output.actor_comment, + actor_comment_lines: output.actor_comment_lines, tests: output.tests, }; hbs.render_template(template, &data).unwrap() diff --git a/rust/candid_parser/src/bindings/rust_agent.hbs b/rust/candid_parser/src/bindings/rust_agent.hbs index 83da7cb57..9d0f3b3b1 100644 --- a/rust/candid_parser/src/bindings/rust_agent.hbs +++ b/rust/candid_parser/src/bindings/rust_agent.hbs @@ -6,15 +6,15 @@ type Result = std::result::Result; {{type_defs}} {{#if methods}} -{{#if actor_comment}} -{{actor_comment}} -{{/if}} +{{#each actor_comment_lines}} +{{this}} +{{/each}} pub struct {{PascalCase service_name}}<'a>(pub Principal, pub &'a ic_agent::Agent); impl<'a> {{PascalCase service_name}}<'a> { {{#each methods}} - {{#if this.comment}} - {{this.comment}} - {{/if}} + {{#each this.comment_lines}} + {{this}} + {{/each}} pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: &{{this.1}}{{/each}}) -> Result<{{vec_to_arity this.rets}}> { let args = Encode!({{#each this.args}}&{{this.0}}{{#unless @last}},{{/unless}}{{/each}})?; let bytes = self.1.{{#if (eq this.mode "update")}}update{{else}}query{{/if}}(&self.0, "{{escape_debug this.original_name}}").with_arg(args).{{#if (eq this.mode "update")}}call_and_wait{{else}}call{{/if}}().await?; diff --git a/rust/candid_parser/src/bindings/rust_call.hbs b/rust/candid_parser/src/bindings/rust_call.hbs index 83d11db94..869c103a1 100644 --- a/rust/candid_parser/src/bindings/rust_call.hbs +++ b/rust/candid_parser/src/bindings/rust_call.hbs @@ -6,15 +6,15 @@ use ic_cdk::api::call::CallResult as Result; {{type_defs}} {{#if methods}} -{{#if actor_comment}} -{{actor_comment}} -{{/if}} +{{#each actor_comment_lines}} +{{this}} +{{/each}} pub struct {{PascalCase service_name}}(pub Principal); impl {{PascalCase service_name}} { {{#each methods}} - {{#if this.comment}} - {{this.comment}} - {{/if}} + {{#each this.comment_lines}} + {{this}} + {{/each}} pub async fn {{this.name}}(&self{{#each this.args}}, {{this.0}}: &{{this.1}}{{/each}}) -> Result<({{#each this.rets}}{{this}},{{/each}})> { ic_cdk::call(self.0, "{{escape_debug this.original_name}}", ({{#each this.args}}{{this.0}},{{/each}})).await } @@ -23,9 +23,9 @@ impl {{PascalCase service_name}} { {{#if canister_id}} /// Canister ID: `{{canister_id}}` pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); -{{#if actor_comment}} -{{actor_comment}} -{{/if}} +{{#each actor_comment_lines}} +{{this}} +{{/each}} pub const {{snake_case service_name}} : {{PascalCase service_name}} = {{PascalCase service_name}}(CANISTER_ID); {{/if}} {{/if}} diff --git a/rust/candid_parser/src/bindings/rust_stub.hbs b/rust/candid_parser/src/bindings/rust_stub.hbs index 27a4564d3..0c27e7e77 100644 --- a/rust/candid_parser/src/bindings/rust_stub.hbs +++ b/rust/candid_parser/src/bindings/rust_stub.hbs @@ -11,9 +11,9 @@ fn init({{#each init_args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{this.1}}{{ } {{/if}} {{#each methods}} -{{#if this.comment}} -{{this.comment}} -{{/if}} +{{#each this.comment_lines}} +{{this}} +{{/each}} #[ic_cdk::{{cdk_attribute this.mode this.name this.original_name}}] fn {{this.name}}({{#each this.args}}{{#if (not @first)}}, {{/if}}{{this.0}}: {{this.1}}{{/each}}) -> {{vec_to_arity this.rets}} { unimplemented!() diff --git a/rust/candid_parser/src/token.rs b/rust/candid_parser/src/token.rs index 22ca2e8c2..ca2c1cbd9 100644 --- a/rust/candid_parser/src/token.rs +++ b/rust/candid_parser/src/token.rs @@ -7,7 +7,7 @@ use logos::{Lexer, Logos}; pub enum Token { #[token("/*")] StartComment, - #[regex(r"(//[^\n]*\n)+", parse_line_comment)] + #[regex(r"(( *)\/\/[^\n]*\n)+", parse_line_comment)] LineComment(String), #[token("=")] Equals, @@ -125,8 +125,7 @@ fn parse_number(lex: &mut Lexer) -> String { fn parse_line_comment(lex: &mut Lexer) -> String { lex.slice() .lines() - // remove the leading "//" and trim any space/newline - .map(|s| s[2..].trim()) + .map(|s| s.trim().trim_start_matches("//").trim()) .collect::>() .join("\n") } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index d8f801660..d10216db4 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -307,7 +307,7 @@ fn merge_actor( check_unique(ms.iter().map(|m| &m.0)).map_err(|e| { Error::msg(format!("Duplicate imported method name: {e}")) })?; - let res: Type = TypeInner::Service(ms).into(); + let res: Type = (TypeInner::Service(ms), t.comment()).into(); Ok(Some(res)) } }, diff --git a/rust/candid_parser/tests/assets/example.did b/rust/candid_parser/tests/assets/example.did index a5a47fee6..80a78c833 100644 --- a/rust/candid_parser/tests/assets/example.did +++ b/rust/candid_parser/tests/assets/example.did @@ -1,22 +1,47 @@ +// This is an ignored import service comment import service "recursion.did"; +// This is an ignored import comment import "import/a.did"; +// This is another ignored import service comment import service "import/b/b.did"; +// This is a type comment type my_type = principal; -type List = opt record { head: int; tail: List }; + +// This is an ignored comment + +type List = opt record { + head: int; + // This is a field comment + tail: List; +}; type f = func (List, func (int32) -> (int64)) -> (opt List, res); +// This is another type comment type broker = service { find : (name: text) -> (service {up:() -> (); current:() -> (nat32)}); }; type nested = record { nat; nat; record {nat;int;}; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; }; 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 nested_res = variant { + // This is a variant comment + Ok : variant { Ok; Err }; + // This is another variant comment + // that spans multiple lines for variants + Err : variant { Ok: record { content: text }; Err: record {int} }; +}; + +// This is another ignored comment +// This is a service comment +// that spans multiple lines for services service server : { f1 : (list, test: blob, opt bool) -> () oneway; + // This is a method comment g1 : (my_type, List, opt List, nested) -> (int, broker, nested_res) query; h : (vec opt text, variant { A: nat; B: opt text }, opt List) -> (record { id: nat; 0x2a: record {} }); i : f; + // This is another method comment + // that spans multiple lines for methods x : (a,b) -> (opt a, opt b, variant { Ok: record { result: text }; Err: variant {a;b} }) composite_query; } diff --git a/rust/candid_parser/tests/assets/import/a.did b/rust/candid_parser/tests/assets/import/a.did index ebdcddc77..499b42f2f 100644 --- a/rust/candid_parser/tests/assets/import/a.did +++ b/rust/candid_parser/tests/assets/import/a.did @@ -1,5 +1,6 @@ import "b/b.did"; type a = variant {a;b:b}; service : (a,b) -> { + // This comment is ignored because this method f : (b) -> (a); } diff --git a/rust/candid_parser/tests/assets/import/b/b.did b/rust/candid_parser/tests/assets/import/b/b.did index b28cb7773..ac6482d96 100644 --- a/rust/candid_parser/tests/assets/import/b/b.did +++ b/rust/candid_parser/tests/assets/import/b/b.did @@ -1,4 +1,6 @@ type b = record { int;nat }; +// This service comment is always ignored because it's in an imported file service : { + // This is a method comment on an imported service bbbbb : (b) -> (); } diff --git a/rust/candid_parser/tests/assets/management.did b/rust/candid_parser/tests/assets/management.did index 24965a650..756226db2 100644 --- a/rust/candid_parser/tests/assets/management.did +++ b/rust/candid_parser/tests/assets/management.did @@ -35,8 +35,10 @@ type ecdsa_curve = variant { secp256k1; }; type satoshi = nat64; type bitcoin_network = variant { - mainnet; // This is a variant comment + mainnet; + // This is another variant comment + // that spans multiple lines for variants testnet; }; diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index a3975f37b..a23456acb 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -4,13 +4,27 @@ import type { IDL } from '@dfinity/candid'; export type A = B; export type B = [] | [A]; -export type List = [] | [{ 'head' : bigint, 'tail' : List }]; +export type List = [] | [ + { + 'head' : bigint, + /** + * This is a field comment + */ + 'tail' : List, + } +]; export type a = { 'a' : null } | { 'b' : b }; export type b = [bigint, bigint]; +/** + * This is another type comment + */ export interface broker { 'find' : ActorMethod<[string], Principal> } export type f = ActorMethod<[List, [Principal, string]], [[] | [List], res]>; export type list = [] | [node]; +/** + * This is a type comment + */ export type my_type = Principal; export interface nested { _0_ : bigint, @@ -24,8 +38,21 @@ export interface nested { { 'C' : null }, _42_ : bigint, } -export type nested_res = { 'Ok' : { 'Ok' : null } | { 'Err' : null } } | - { 'Err' : { 'Ok' : { 'content' : string } } | { 'Err' : [bigint] } }; +export type nested_res = { + /** + * This is a variant comment + */ + 'Ok' : { 'Ok' : null } | + { 'Err' : null } + } | + { + /** + * This is another variant comment + * that spans multiple lines for variants + */ + 'Err' : { 'Ok' : { 'content' : string } } | + { 'Err' : [bigint] } + }; export interface node { 'head' : bigint, 'tail' : list } export type res = { 'Ok' : [bigint, bigint] } | { 'Err' : { 'error' : string } }; @@ -36,11 +63,21 @@ export type tree = { 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } } | { 'leaf' : bigint }; +/** + * This is a service comment + * that spans multiple lines for services + */ export interface _SERVICE { + /** + * This is a method comment on an imported service + */ 'bbbbb' : ActorMethod<[b], undefined>, 'f' : t, 'f1' : ActorMethod<[list, Uint8Array | number[], [] | [boolean]], undefined>, 'g' : ActorMethod<[list], [B, tree, stream]>, + /** + * This is a method comment + */ 'g1' : ActorMethod< [my_type, List, [] | [List], nested], [bigint, Principal, nested_res] @@ -55,6 +92,10 @@ export interface _SERVICE { { _42_ : {}, 'id' : bigint } >, 'i' : f, + /** + * This is another method comment + * that spans multiple lines for methods + */ 'x' : ActorMethod< [a, b], [ diff --git a/rust/candid_parser/tests/assets/ok/example.did b/rust/candid_parser/tests/assets/ok/example.did index 6bd434f09..c8ba0257e 100644 --- a/rust/candid_parser/tests/assets/ok/example.did +++ b/rust/candid_parser/tests/assets/ok/example.did @@ -1,13 +1,19 @@ type A = B; type B = opt A; -type List = opt record { head : int; tail : List }; +type List = opt record { + head : int; + // This is a field comment + tail : List; +}; type a = variant { a; b : b }; type b = record { int; nat }; +// This is another type comment type broker = service { find : (name : text) -> (service { current : () -> (nat32); up : () -> () }); }; type f = func (List, func (int32) -> (int64)) -> (opt List, res); type list = opt node; +// This is a type comment type my_type = principal; type nested = record { 0 : nat; @@ -19,7 +25,10 @@ type nested = record { 42 : nat; }; type nested_res = variant { + // This is a variant comment Ok : variant { Ok; Err }; + // This is another variant comment + // that spans multiple lines for variants Err : variant { Ok : record { content : text }; Err : record { int } }; }; type node = record { head : nat; tail : list }; @@ -31,16 +40,22 @@ type tree = variant { branch : record { val : int; left : tree; right : tree }; leaf : int; }; +// This is a service comment +// that spans multiple lines for services service : { + // This is a method comment on an imported service bbbbb : (b) -> (); f : t; f1 : (list, test : blob, opt bool) -> () oneway; g : (list) -> (B, tree, stream); + // This is a method comment g1 : (my_type, List, opt List, nested) -> (int, broker, nested_res) query; h : (vec opt text, variant { A : nat; B : opt text }, opt List) -> ( record { 42 : record {}; id : nat }, ); i : f; + // This is another method comment + // that spans multiple lines for methods x : (a, b) -> ( opt a, opt b, diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 76bb1f058..00bb39a19 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -7,6 +7,7 @@ module { public type List = ?{ head : Int; tail : List }; public type a = { #a; #b : b }; public type b = (Int, Nat); + /// This is another type comment public type broker = actor { find : shared (name : Text) -> async actor { current : shared () -> async Nat32; @@ -18,6 +19,7 @@ module { res, ); public type list = ?node; + /// This is a type comment public type my_type = Principal; public type nested = { _0_ : Nat; @@ -29,7 +31,10 @@ module { _42_ : Nat; }; public type nested_res = { + /// This is a variant comment #Ok : { #Ok; #Err }; + /// This is another variant comment + /// that spans multiple lines for variants #Err : { #Ok : { content : Text }; #Err : { _0_ : Int } }; }; public type node = { head : Nat; tail : list }; @@ -41,11 +46,15 @@ module { #branch : { val : Int; left : tree; right : tree }; #leaf : Int; }; + /// This is a service comment + /// that spans multiple lines for services public type Self = actor { + /// This is a method comment on an imported service bbbbb : shared b -> async (); f : t; f1 : shared (list, test : Blob, ?Bool) -> (); g : shared list -> async (B, tree, stream); + /// This is a method comment g1 : shared query (my_type, List, ?List, nested) -> async ( Int, broker, @@ -56,6 +65,8 @@ module { id : Nat; }; i : f; + /// This is another method comment + /// that spans multiple lines for methods x : shared composite query (a, b) -> async ( ?a, ?b, diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index fcb69b857..1da7f244a 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -33,12 +33,14 @@ candid::define_service!(pub(crate) S : { "g" : candid::func!((List) -> (B, Tree, Stream)); }); candid::define_function!(pub(crate) T : (S) -> ()); +/// This is a type comment type CanisterId = Principal; #derive[CandidType, Deserialize, Clone] pub(crate) struct ListInner { #[serde(skip_deserializing)] #[serde(rename="head")] HEAD: candid::Int, + /// This is a field comment #[serde(skip_deserializing)] tail: Arc, } @@ -73,6 +75,7 @@ candid::define_service!(pub BrokerReturn : { "current" : candid::func!(() -> (u32)); "up" : candid::func!(() -> ()); }); +/// This is another type comment candid::define_service!(pub(crate) Broker : { "find" : candid::func!((String) -> (BrokerReturn)); }); @@ -102,8 +105,11 @@ pub(crate) struct XRet2Ok { pub(crate) result: String } #[derive(CandidType, Deserialize, Debug)] pub(crate) enum Error { #[serde(rename="a")] A, #[serde(rename="b")] B } +/// This is a service comment +/// that spans multiple lines for services pub struct Service(pub Principal); impl Service { + /// This is a method comment on an imported service pub async fn bbbbb(&self, arg0: &B) -> Result<()> { ic_cdk::call(self.0, "bbbbb", (arg0,)).await } @@ -116,6 +122,7 @@ impl Service { pub async fn g(&self, arg0: &List) -> Result<(B,Tree,Stream,)> { ic_cdk::call(self.0, "g", (arg0,)).await } + /// This is a method comment pub async fn G11(&self, id: &CanisterId, list: &MyList, is_okay: &Option, arg3: &Nested) -> Result<(i128,Broker,NestedRes,)> { ic_cdk::call(self.0, "g1", (id,list,is_okay,arg3,)).await } @@ -125,18 +132,26 @@ impl Service { pub async fn i(&self, arg0: &MyList, arg1: &FArg1) -> Result<(Option,Res,)> { ic_cdk::call(self.0, "i", (arg0,arg1,)).await } + /// This is another method comment + /// that spans multiple lines for methods pub async fn x(&self, arg0: &A, arg1: &B) -> Result<(Option,Option,std::result::Result,)> { ic_cdk::call(self.0, "x", (arg0,arg1,)).await } } /// Canister ID: `aaaaa-aa` pub const CANISTER_ID : Principal = Principal::from_slice(&[]); +/// This is a service comment +/// that spans multiple lines for services 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 }; +type ListInner = record { + head : int; + // This is a field comment + tail : List; +}; (List)"#; candid_parser::utils::check_rust_type::>(candid_src).unwrap(); } diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index fc8ef4530..e986e8ecc 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -3,11 +3,17 @@ import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; export type bitcoin_address = string; -export type bitcoin_network = { 'mainnet' : null } | - { +export type bitcoin_network = { /** * This is a variant comment */ + 'mainnet' : null + } | + { + /** + * This is another variant comment + * that spans multiple lines for variants + */ 'testnet' : null }; export type block_hash = Uint8Array | number[]; diff --git a/rust/candid_parser/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did index 5dfe89c81..f43aca1fb 100644 --- a/rust/candid_parser/tests/assets/ok/management.did +++ b/rust/candid_parser/tests/assets/ok/management.did @@ -1,7 +1,9 @@ type bitcoin_address = text; type bitcoin_network = variant { - mainnet; // This is a variant comment + mainnet; + // This is another variant comment + // that spans multiple lines for variants testnet; }; type block_hash = blob; diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 7df60f0ca..d44ebd4da 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -4,8 +4,10 @@ module { public type bitcoin_address = Text; public type bitcoin_network = { - #mainnet; /// This is a variant comment + #mainnet; + /// This is another variant comment + /// that spans multiple lines for variants #testnet; }; public type block_hash = Blob; diff --git a/rust/candid_parser/tests/assets/ok/management.rs b/rust/candid_parser/tests/assets/ok/management.rs index d8bbd575a..e38cc7f88 100644 --- a/rust/candid_parser/tests/assets/ok/management.rs +++ b/rust/candid_parser/tests/assets/ok/management.rs @@ -6,9 +6,11 @@ type Result = std::result::Result; #[derive(CandidType, Deserialize)] pub enum BitcoinNetwork { + /// This is a variant comment #[serde(rename="mainnet")] Mainnet, - /// This is a variant comment + /// This is another variant comment + /// that spans multiple lines for variants #[serde(rename="testnet")] Testnet, } From 705f603a1c05bf963ba9aa32a461bee55a778521 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 12 Jun 2025 21:51:29 +0200 Subject: [PATCH 09/23] feat: support comments from rust --- rust/candid/src/binary_parser.rs | 1 + rust/candid/src/de.rs | 4 +- rust/candid/src/pretty/candid.rs | 2 +- rust/candid/src/ser.rs | 4 +- rust/candid/src/types/impls.rs | 10 +- rust/candid/src/types/internal.rs | 37 +++++--- rust/candid/src/types/result.rs | 2 + rust/candid/src/types/subtype.rs | 9 +- rust/candid/src/types/value.rs | 4 +- rust/candid/tests/types.rs | 42 +++++++-- rust/candid_derive/src/derive.rs | 44 ++++++--- rust/candid_derive/src/func.rs | 91 ++++++++++++------- rust/candid_derive/src/lib.rs | 32 +++++++ rust/candid_parser/src/bindings/motoko.rs | 7 +- rust/candid_parser/src/bindings/rust.rs | 38 +++++--- rust/candid_parser/src/bindings/typescript.rs | 2 +- rust/candid_parser/src/random.rs | 4 +- rust/candid_parser/src/typing.rs | 3 +- 18 files changed, 246 insertions(+), 90 deletions(-) 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 ca770a776..e4181a8c8 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -141,7 +141,7 @@ pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { } else { kwd(" :").append(pp_ty(&field.ty)) }; - pp_comment(field.ty.comment()) + pp_comment(field.comment.as_ref()) .append(pp_label(&field.id)) .append(ty_doc) } 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 77484144a..c1386780b 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -95,20 +95,25 @@ impl TypeContainer { let t = T::ty(); self.go(&t) } + pub fn add_with_comment(&mut self, comment: Option<&String>) -> Type { + let t = T::ty().with_comment(comment); + self.go(&t) + } fn go(&mut self, t: &Type) -> Type { 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 inner = 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(), ); - let res = (inner, t.comment()).into(); + let res = (record_inner, t.comment()).into(); if t.is_tuple() { return res; } @@ -123,15 +128,16 @@ impl TypeContainer { } } TypeInner::Variant(fs) => { - let inner = 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(), ); - let res = (inner, t.comment()).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); @@ -279,17 +285,19 @@ impl Type { Vec(t) => Vec(t.subst(tau)), Record(fs) => Record( fs.iter() - .map(|Field { id, ty }| Field { + .map(|Field { id, ty, comment }| Field { id: id.clone(), ty: ty.subst(tau), + comment: comment.clone(), }) .collect(), ), Variant(fs) => Variant( fs.iter() - .map(|Field { id, ty }| Field { + .map(|Field { id, ty, comment }| Field { id: id.clone(), ty: ty.subst(tau), + comment: comment.clone(), }) .collect(), ), @@ -329,6 +337,9 @@ impl Type { pub fn comment(&self) -> Option<&String> { self.1.as_ref() } + pub fn with_comment(&self, comment: Option<&String>) -> Self { + Self(self.0.clone(), comment.cloned()) + } } #[cfg(feature = "printer")] impl fmt::Display for Type { @@ -488,6 +499,7 @@ pub type SharedLabel = std::rc::Rc