From 031bfc2f445adec1d5c5aaeffb0aa9eebbc636f0 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 9 Oct 2025 16:03:09 +0200 Subject: [PATCH 1/6] fix: restore parsing behavior changed in #612 Named arguments can also be in the return type, and can be ignored. This PR restores that was changed in https://github.com/dfinity/candid/pull/612/files#diff-c5ff9610c5d6538ec79f3d465965d2b3a3e3b628b16854a91b4b4a90ba766a57L220-L230, in order to keep parsing the return types with arguments without throwing. Updates a test case to check if the behavior has been restored. --- rust/candid_parser/src/grammar.lalrpop | 17 +++++++++++------ rust/candid_parser/tests/assets/service.did | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index cfcdd2c73..7e1139bf2 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -217,7 +217,7 @@ VariantFieldTyp: TypeField = { =>? Ok(TypeField { label: Label::Id(id), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() }), } -ArgTupTyp: Vec = "(" > ")" =>? { +NamedArgTupTyp: Vec = "(" > ")" =>? { let args = <>; let mut named_args: Vec = args.iter().filter_map(|a| a.name.clone()).collect(); named_args.sort(); @@ -225,14 +225,19 @@ ArgTupTyp: Vec = "(" > ")" =>? { Ok(args) }; -TupTyp: Vec = "(" > ")" => <>; +TupTyp: Vec = "(" > ")" => <>; FuncTyp: FuncType = { - "->" => + "->" => FuncType { modes, args, rets }, } -ArgTyp: IDLArgType = { +ArgTyp: IDLType = { + Typ => <>, + Name ":" => <>, +} + +NamedArgTyp: IDLArgType = { => IDLArgType::new(t), ":" => IDLArgType::new_with_name(t, n), } @@ -271,7 +276,7 @@ Actor: IDLType = { MainActor: IDLActorType = { "service" "id"? ":" ";"? => IDLActorType { typ: t, docs: doc_comment.unwrap_or_default() }, - "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, + "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, } pub IDLProg: IDLProg = { @@ -279,7 +284,7 @@ pub IDLProg: IDLProg = { } pub IDLInitArgs: IDLInitArgs = { - > => IDLInitArgs { decs, args } + > => IDLInitArgs { decs, args } } // Test file. Follows the "specification" in test/README.md diff --git a/rust/candid_parser/tests/assets/service.did b/rust/candid_parser/tests/assets/service.did index cfec74e3f..43e2602bc 100644 --- a/rust/candid_parser/tests/assets/service.did +++ b/rust/candid_parser/tests/assets/service.did @@ -5,5 +5,5 @@ service : { asArray: () -> (vec Service2, vec Func) query; asRecord: () -> (record { Service2; opt Service; Func }); asVariant: () -> (variant { a: Service2; b: record { f:opt Func }}); - asPrincipal: () -> (Service2, Func); + asPrincipal: () -> (ret0 : Service2, ret1 : Func); } From e472e4f8c919651f518672a205cf6716c0a24e93 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 9 Oct 2025 16:09:18 +0200 Subject: [PATCH 2/6] refactor: move new code up --- rust/candid_parser/src/grammar.lalrpop | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 7e1139bf2..6cb024e24 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -225,6 +225,11 @@ NamedArgTupTyp: Vec = "(" > ")" =>? { Ok(args) }; +NamedArgTyp: IDLArgType = { + => IDLArgType::new(t), + ":" => IDLArgType::new_with_name(t, n), +} + TupTyp: Vec = "(" > ")" => <>; FuncTyp: FuncType = { @@ -237,11 +242,6 @@ ArgTyp: IDLType = { Name ":" => <>, } -NamedArgTyp: IDLArgType = { - => IDLArgType::new(t), - ":" => IDLArgType::new_with_name(t, n), -} - FuncMode: FuncMode = { "oneway" => FuncMode::Oneway, "query" => FuncMode::Query, From e5c4f9e1989398bb62abf3013cb72c1c34327691 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 9 Oct 2025 17:12:45 +0200 Subject: [PATCH 3/6] feat: collect names for return types --- rust/candid/src/binary_parser.rs | 5 +++- rust/candid/src/pretty/candid.rs | 7 +---- rust/candid/src/ser.rs | 4 +-- rust/candid/src/types/internal.rs | 30 ++++++++++++++----- rust/candid/src/types/subtype.rs | 28 ++++++++++++++--- rust/candid_derive/src/func.rs | 4 +-- rust/candid_parser/src/bindings/analysis.rs | 8 +++-- rust/candid_parser/src/bindings/javascript.rs | 18 ++++++----- rust/candid_parser/src/bindings/motoko.rs | 13 ++++---- rust/candid_parser/src/bindings/rust.rs | 17 ++++++----- rust/candid_parser/src/bindings/typescript.rs | 4 +-- rust/candid_parser/src/grammar.lalrpop | 21 +++++-------- rust/candid_parser/src/syntax/mod.rs | 4 +-- rust/candid_parser/src/syntax/pretty.rs | 6 +--- rust/candid_parser/src/test.rs | 6 ++-- rust/candid_parser/src/typing.rs | 2 +- .../candid_parser/tests/assets/ok/service.did | 2 +- rust/candid_parser/tests/value.rs | 9 ++++-- tools/didc/src/main.rs | 9 +++--- 19 files changed, 117 insertions(+), 80 deletions(-) diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index 1c53a8e39..1226f0272 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -205,7 +205,10 @@ impl ConsType { }); } for ret in &f.rets { - rets.push(ret.to_type(len)?); + rets.push(ArgType { + name: None, + typ: ret.to_type(len)?, + }); } TypeInner::Func(Function { modes: f.ann.iter().map(|x| x.inner.clone()).collect(), diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 1d4fe42bd..9f0089663 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -156,7 +156,7 @@ fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { pub fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_named_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_named_args(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") .append(RcDoc::space()) @@ -185,11 +185,6 @@ pub fn pp_args(args: &[Type]) -> RcDoc<'_> { sep_enclose(args.iter().map(pp_ty), ",", "(", ")") } -/// Pretty-prints return types in the form of `(type1, type2)`. -pub fn pp_rets(args: &[Type]) -> RcDoc<'_> { - pp_args(args) -} - pub fn pp_mode(mode: &FuncMode) -> RcDoc<'_> { match mode { FuncMode::Oneway => RcDoc::text("oneway"), diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 53fee18ef..986789eed 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -339,7 +339,7 @@ impl TypeSerialize { self.build_type(&ty.typ)?; } for ty in &func.rets { - self.build_type(ty)?; + self.build_type(&ty.typ)?; } sleb128_encode(&mut buf, Opcode::Func as i64)?; leb128_encode(&mut buf, func.args.len() as u64)?; @@ -348,7 +348,7 @@ impl TypeSerialize { } leb128_encode(&mut buf, func.rets.len() as u64)?; for ty in &func.rets { - self.encode(&mut buf, ty)?; + self.encode(&mut buf, &ty.typ)?; } leb128_encode(&mut buf, func.modes.len() as u64)?; for m in &func.modes { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 62d8ae855..742c87e30 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -163,7 +163,14 @@ impl TypeContainer { typ: self.go(&arg.typ), }) .collect(), - rets: func.rets.iter().map(|arg| self.go(arg)).collect(), + rets: func + .rets + .iter() + .map(|arg| ArgType { + name: arg.name.clone(), + typ: self.go(&arg.typ), + }) + .collect(), }), TypeInner::Service(serv) => TypeInner::Service( serv.iter() @@ -307,7 +314,14 @@ impl Type { typ: t.typ.subst(tau), }) .collect(), - rets: func.rets.into_iter().map(|t| t.subst(tau)).collect(), + rets: func + .rets + .into_iter() + .map(|t| ArgType { + name: t.name, + typ: t.typ.subst(tau), + }) + .collect(), }) } Service(serv) => Service( @@ -398,7 +412,7 @@ pub fn text_size(t: &Type, limit: i32) -> Result { limit -= cnt; } for t in &func.rets { - cnt += text_size(t, limit)?; + cnt += text_size(&t.typ, limit)?; limit -= cnt; } cnt @@ -552,7 +566,7 @@ pub enum FuncMode { pub struct Function { pub modes: Vec, pub args: Vec, - pub rets: Vec, + pub rets: Vec, } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -587,16 +601,16 @@ impl Function { /// `func!((u8, &str) -> (Nat) query)` expands to `Type(Rc::new(TypeInner::Func(...)))` macro_rules! func { ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Query] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::Query] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) composite_query ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::CompositeQuery] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::CompositeQuery] })) }; ( ( $($arg:ty),* $(,)? ) -> ( $($ret:ty),* $(,)? ) oneway ) => { - Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*], modes: vec![$crate::types::FuncMode::Oneway] })) + Into::<$crate::types::Type>::into($crate::types::TypeInner::Func($crate::types::Function { args: vec![$(<$arg>::ty()),*].into_iter().map(|arg| $crate::types::ArgType { name: None, typ: arg }).collect(), rets: vec![$(<$ret>::ty()),*].into_iter().map(|ret| $crate::types::ArgType { name: None, typ: ret }).collect(), modes: vec![$crate::types::FuncMode::Oneway] })) }; } #[macro_export] diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 11fad4123..947e777f5 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -139,10 +139,20 @@ fn subtype_( .iter() .map(|arg| arg.typ.clone()) .collect::>(); + let f1_rets = f1 + .rets + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let f2_rets = f2 + .rets + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); let args1 = to_tuple(&f1_args); let args2 = to_tuple(&f2_args); - let rets1 = to_tuple(&f1.rets); - let rets2 = to_tuple(&f2.rets); + let rets1 = to_tuple(&f1_rets); + let rets2 = to_tuple(&f2_rets); subtype_(report, gamma, env, &args2, &args1) .context("Subtype fails at function input type")?; subtype_(report, gamma, env, &rets1, &rets2) @@ -232,10 +242,20 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( .iter() .map(|arg| arg.typ.clone()) .collect::>(); + let f1_rets = f1 + .rets + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); + let f2_rets = f2 + .rets + .iter() + .map(|arg| arg.typ.clone()) + .collect::>(); let args1 = to_tuple(&f1_args); let args2 = to_tuple(&f2_args); - let rets1 = to_tuple(&f1.rets); - let rets2 = to_tuple(&f2.rets); + let rets1 = to_tuple(&f1_rets); + let rets2 = to_tuple(&f2_rets); equal(gamma, env, &args1, &args2).context("Mismatch in function input type")?; equal(gamma, env, &rets1, &rets2).context("Mismatch in function return type")?; Ok(()) diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 1b4487aa6..7e5443ccd 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -140,7 +140,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { #doc_storage let mut args: Vec = Vec::new(); #(#args)* - let mut rets: Vec = Vec::new(); + let mut rets: Vec = Vec::new(); #(#rets)* let func = Function { args, rets, modes: #modes }; service.push((#name.to_string(), TypeInner::Func(func).into())); @@ -195,7 +195,7 @@ fn generate_arg(name: TokenStream, (arg_name, ty): &(Option, String)) -> fn generate_ret(name: TokenStream, ty: &str) -> TokenStream { let ty = syn::parse_str::(ty).unwrap(); quote! { - #name.push(env.add::<#ty>()); + #name.push(ArgType { name: None, typ: env.add::<#ty>() }); } } diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index 01af68de9..cf14e2e76 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -58,7 +58,8 @@ pub fn chase_type<'a>( } Func(f) => { let args = f.args.iter().map(|arg| &arg.typ); - for ty in args.clone().chain(f.rets.iter()) { + let rets = f.rets.iter().map(|ret| &ret.typ); + for ty in args.chain(rets) { chase_type(seen, res, env, ty)?; } } @@ -117,7 +118,7 @@ pub fn chase_def_use<'a>( } for (i, arg) in func.rets.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -162,7 +163,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result { let args = f.args.iter().map(|arg| &arg.typ); - for ty in args.clone().chain(f.rets.iter()) { + let rets = f.rets.iter().map(|ret| &ret.typ); + for ty in args.chain(rets) { go(seen, res, _env, ty)?; } } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index ad750c9d1..92b6a7a98 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -161,18 +161,20 @@ fn pp_fields(fs: &[Field]) -> RcDoc<'_> { fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_args(&func.rets); let modes = pp_modes(&func.modes); sep_enclose([args, rets, modes], ",", "(", ")").nest(INDENT_SPACE) } fn pp_args(args: &[ArgType]) -> RcDoc<'_> { - let args = args.iter().map(|arg| pp_ty(&arg.typ)); - sep_enclose(args, ",", "[", "]") + pp_types(args.iter().map(|arg| &arg.typ)) } -fn pp_rets(args: &[Type]) -> RcDoc<'_> { - sep_enclose(args.iter().map(pp_ty), ",", "[", "]") +fn pp_types<'a, T>(types: T) -> RcDoc<'a> +where + T: Iterator, +{ + sep_enclose(types.map(pp_ty), ",", "[", "]") } fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc<'_> { @@ -275,7 +277,7 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { .append(";"); let idl_init_args = str("export const idlInitArgs = ") - .append(pp_rets(init_types)) + .append(pp_types(init_types.iter())) .append(";"); let idl_factory_return = kwd("return").append(actor).append(";"); @@ -286,7 +288,9 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { let init_defs = chase_types(env, init_types).unwrap(); let init_recs = infer_rec(env, &init_defs).unwrap(); let init_defs_doc = pp_defs(env, &init_defs, &init_recs, false); - let init_doc = kwd("return").append(pp_rets(init_types)).append(";"); + let init_doc = kwd("return") + .append(pp_types(init_types.iter())) + .append(";"); let init_doc = init_defs_doc.append(init_doc); let init_doc = str("export const init = ({ IDL }) => ").append(enclose_space("{", init_doc, "};")); diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 49a5a6fa2..0408b3a94 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -212,16 +212,17 @@ fn pp_args(args: &[ArgType]) -> RcDoc<'_> { } } -fn pp_rets(args: &[Type]) -> RcDoc<'_> { - match args { +fn pp_rets(rets: &[ArgType]) -> RcDoc<'_> { + match rets { [ty] => { - if is_tuple(ty) { - enclose("(", pp_ty(ty), ")") + let typ = &ty.typ; + if is_tuple(typ) { + enclose("(", pp_ty(typ), ")") } else { - pp_ty(ty) + pp_ty(typ) } } - _ => sep_enclose(args.iter().map(pp_ty), ",", "(", ")"), + _ => sep_enclose(rets.iter().map(|ret| pp_ty(&ret.typ)), ",", "(", ")"), } } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 88e609099..c21449ae9 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -619,11 +619,11 @@ fn test_{test_name}() {{ }); sep_enclose(args, ",", "(", ")") } - fn pp_rets<'b>(&mut self, rets: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + fn pp_rets<'b>(&mut self, rets: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { let tys = rets.iter().enumerate().map(|(i, t)| { let lab = format!("{prefix}{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = self.pp_ty(t, true); + let res = self.pp_ty(&t.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }); @@ -701,10 +701,10 @@ fn test_{test_name}() {{ .rets .iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, ret)| { let lab = format!("ret{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = self.pp_ty(ty, true); + let res = self.pp_ty(&ret.typ, true); self.state.pop_state(old, StateElem::Label(&lab)); res }) @@ -1111,7 +1111,7 @@ impl<'b> NominalState<'_, 'b> { .rets .into_iter() .enumerate() - .map(|(i, ty)| { + .map(|(i, ret)| { let lab = format!("ret{i}"); let old = self.state.push_state(&StateElem::Label(&lab)); let idx = if i == 0 { @@ -1120,10 +1120,13 @@ impl<'b> NominalState<'_, 'b> { i.to_string() }; path.push(TypePath::Func(format!("ret{idx}"))); - let ty = self.nominalize(env, path, &ty, None); + let ty = self.nominalize(env, path, &ret.typ, None); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - ty + ArgType { + name: ret.name.clone(), + typ: ty, + } }) .collect(), }) diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index b53adee40..f5991791a 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -207,9 +207,9 @@ fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { let args = sep_enclose(args, ",", "[", "]"); let rets = match func.rets.len() { 0 => str("undefined"), - 1 => pp_ty(env, &func.rets[0], true), + 1 => pp_ty(env, &func.rets[0].typ, true), _ => sep_enclose( - func.rets.iter().map(|ty| pp_ty(env, ty, true)), + func.rets.iter().map(|ty| pp_ty(env, &ty.typ, true)), ",", "[", "]", diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 6cb024e24..dea382f75 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -217,7 +217,7 @@ VariantFieldTyp: TypeField = { =>? Ok(TypeField { label: Label::Id(id), typ: IDLType::PrimT(PrimType::Null), docs: doc_comment.unwrap_or_default() }), } -NamedArgTupTyp: Vec = "(" > ")" =>? { +TupTyp: Vec = "(" > ")" =>? { let args = <>; let mut named_args: Vec = args.iter().filter_map(|a| a.name.clone()).collect(); named_args.sort(); @@ -225,21 +225,14 @@ NamedArgTupTyp: Vec = "(" > ")" =>? { Ok(args) }; -NamedArgTyp: IDLArgType = { - => IDLArgType::new(t), - ":" => IDLArgType::new_with_name(t, n), -} - -TupTyp: Vec = "(" > ")" => <>; - FuncTyp: FuncType = { - "->" => + "->" => FuncType { modes, args, rets }, } -ArgTyp: IDLType = { - Typ => <>, - Name ":" => <>, +ArgTyp: IDLArgType = { + => IDLArgType::new(t), + ":" => IDLArgType::new_with_name(t, n), } FuncMode: FuncMode = { @@ -276,7 +269,7 @@ Actor: IDLType = { MainActor: IDLActorType = { "service" "id"? ":" ";"? => IDLActorType { typ: t, docs: doc_comment.unwrap_or_default() }, - "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, + "service" "id"? ":" "->" ";"? => IDLActorType { typ: IDLType::ClassT(args, Box::new(t)), docs: doc_comment.unwrap_or_default() }, } pub IDLProg: IDLProg = { @@ -284,7 +277,7 @@ pub IDLProg: IDLProg = { } pub IDLInitArgs: IDLInitArgs = { - > => IDLInitArgs { decs, args } + > => IDLInitArgs { decs, args } } // Test file. Follows the "specification" in test/README.md diff --git a/rust/candid_parser/src/syntax/mod.rs b/rust/candid_parser/src/syntax/mod.rs index 3ff42cdbf..f8caf3442 100644 --- a/rust/candid_parser/src/syntax/mod.rs +++ b/rust/candid_parser/src/syntax/mod.rs @@ -51,7 +51,7 @@ impl std::str::FromStr for IDLType { #[derive(Debug, Clone)] pub struct IDLTypes { - pub args: Vec, + pub args: Vec, } impl std::str::FromStr for IDLTypes { @@ -107,7 +107,7 @@ pub enum PrimType { pub struct FuncType { pub modes: Vec, pub args: Vec, - pub rets: Vec, + pub rets: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/rust/candid_parser/src/syntax/pretty.rs b/rust/candid_parser/src/syntax/pretty.rs index 7480d336f..8c89f7c1c 100644 --- a/rust/candid_parser/src/syntax/pretty.rs +++ b/rust/candid_parser/src/syntax/pretty.rs @@ -88,7 +88,7 @@ fn pp_function(func: &FuncType) -> RcDoc<'_> { fn pp_method(func: &FuncType) -> RcDoc<'_> { let args = pp_args(&func.args); - let rets = pp_rets(&func.rets); + let rets = pp_args(&func.rets); let modes = pp_modes(&func.modes); args.append(" ->") .append(RcDoc::space()) @@ -107,10 +107,6 @@ fn pp_args(args: &[IDLArgType]) -> RcDoc<'_> { sep_enclose(args, ",", "(", ")") } -fn pp_rets(rets: &[IDLType]) -> RcDoc<'_> { - sep_enclose(rets.iter().map(pp_ty), ",", "(", ")") -} - fn pp_service(methods: &[Binding]) -> RcDoc<'_> { kwd("service").append(pp_service_methods(methods)) } diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index 10735e941..a25ea91b7 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -1,5 +1,5 @@ use super::typing::check_prog; -use crate::syntax::{Dec, IDLProg, IDLType}; +use crate::syntax::{Dec, IDLArgType, IDLProg}; use crate::{Error, Result}; use candid::types::value::IDLArgs; use candid::types::{Type, TypeEnv}; @@ -7,7 +7,7 @@ use candid::DecoderConfig; const DECODING_COST: usize = 20_000_000; -type TupType = Vec; +type TupType = Vec; pub struct Test { pub defs: Vec, @@ -158,7 +158,7 @@ pub fn check(test: Test) -> Result<()> { print!("Checking {} {}...", i + 1, assert.desc()); let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push(super::typing::ast_to_type(&env, ty)?); + types.push(super::typing::ast_to_type(&env, &ty.typ)?); } let input = assert.left.parse(&env, &types); let pass = if let Some(assert_right) = &assert.right { diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 264fb4b23..67c2c8249 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -79,7 +79,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { } let mut t2 = Vec::new(); for t in func.rets.iter() { - t2.push(check_type(env, t)?); + t2.push(check_arg(env, t)?); } if func.modes.len() > 1 { return Err(Error::msg("cannot have more than one mode")); diff --git a/rust/candid_parser/tests/assets/ok/service.did b/rust/candid_parser/tests/assets/ok/service.did index 7dc21afac..741e020cc 100644 --- a/rust/candid_parser/tests/assets/ok/service.did +++ b/rust/candid_parser/tests/assets/ok/service.did @@ -3,7 +3,7 @@ type Service2 = Service; type Func = func () -> (Service); service : { asArray : () -> (vec Service2, vec Func) query; - asPrincipal : () -> (Service2, Func); + asPrincipal : () -> (ret0 : Service2, ret1 : Func); asRecord : () -> (record { Service2; opt Service; Func }); asVariant : () -> (variant { a : Service2; b : record { f : opt Func } }); } diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index 6199f8422..80a022ccd 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -66,10 +66,15 @@ service : { { let str = "(opt record { head = 1000; tail = opt record {head = -2000; tail = null}}, variant {a = 42})"; let args = parse_idl_args(str).unwrap(); - let encoded = args.to_bytes_with_types(&env, &method.rets).unwrap(); + let rets = method + .rets + .iter() + .map(|ret| ret.typ.clone()) + .collect::>(); + let encoded = args.to_bytes_with_types(&env, &rets).unwrap(); let decoded = IDLArgs::from_bytes(&encoded).unwrap(); assert_eq!(decoded.to_string(), "(\n opt record {\n 1_158_359_328 = 1_000 : int16;\n 1_291_237_008 = opt record {\n 1_158_359_328 = -2_000 : int16;\n 1_291_237_008 = null;\n };\n },\n variant { 97 = 42 : nat },\n)"); - let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &method.rets).unwrap(); + let decoded = IDLArgs::from_bytes_with_types(&encoded, &env, &rets).unwrap(); assert_eq!( decoded.to_string(), "(\n opt record {\n head = 1_000 : int16;\n tail = opt record { head = -2_000 : int16; tail = null };\n },\n variant { a = 42 : nat },\n)" diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index c6e69114d..5ae280008 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -131,7 +131,7 @@ impl TypeAnnotation { (Some(tys), None) => { let mut types = Vec::new(); for ty in tys.args.iter() { - types.push(ast_to_type(&env, ty)?); + types.push(ast_to_type(&env, &ty.typ)?); } Ok((env, types)) } @@ -139,10 +139,11 @@ impl TypeAnnotation { let actor = actor .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; let func = env.get_method(&actor, meth)?; - let types = match mode { - Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), - Mode::Decode => func.rets.clone(), + let types_iter = match mode { + Mode::Encode => func.args.iter(), + Mode::Decode => func.rets.iter(), }; + let types = types_iter.map(|arg| arg.typ.clone()).collect(); Ok((env, types)) } _ => unreachable!(), From e1168e8c3d663e7fa2cbcdcbc11bd248fc695a15 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 9 Oct 2025 17:19:35 +0200 Subject: [PATCH 4/6] chore: update changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c47a7620..a4e98ad07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * [BREAKING]: type representation was optimized to improve performance: + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. - + The `args` field of the `candid::types::internal::Function` struct now is a `Vec` instead of `Vec`, to preserve argument names. + + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. * [BREAKING]: Removed the `candid::pretty::concat` function @@ -16,7 +16,7 @@ * Non-breaking changes: + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. + The `JavaScript` `didc` target now exports its generated IDL type objects. - + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function altneratives to `idlFactory` and `init`). + + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). + fix: subtyping and coercion rules for optional types + fix: coercion of values into nested optional types + fix: values of types `reserved` at any context do not coerce into values of type `null` @@ -26,6 +26,7 @@ * Breaking changes: + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The `rets` field in `FuncType` now has type `Vec`. * Non-breaking changes: + Supports parsing the arguments' names for `func` and `service` (init args). From 86e7fab8da5aeeef957d18efea59a234a227a05c Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 9 Oct 2025 18:04:49 +0200 Subject: [PATCH 5/6] refactor: unify to_tuple --- rust/candid/src/types/subtype.rs | 76 ++++++-------------------------- 1 file changed, 14 insertions(+), 62 deletions(-) diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 947e777f5..234741173 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -1,5 +1,5 @@ use super::internal::{find_type, Field, Label, Type, TypeInner}; -use crate::types::TypeEnv; +use crate::types::{ArgType, TypeEnv}; use crate::{Error, Result}; use anyhow::Context; use std::collections::{HashMap, HashSet}; @@ -129,30 +129,10 @@ fn subtype_( if f1.modes != f2.modes { return Err(Error::msg("Function mode mismatch")); } - let f1_args = f1 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f2_args = f2 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f1_rets = f1 - .rets - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f2_rets = f2 - .rets - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let args1 = to_tuple(&f1_args); - let args2 = to_tuple(&f2_args); - let rets1 = to_tuple(&f1_rets); - let rets2 = to_tuple(&f2_rets); + let args1 = to_tuple(&f1.args); + let args2 = to_tuple(&f2.args); + let rets1 = to_tuple(&f1.rets); + let rets2 = to_tuple(&f2.rets); subtype_(report, gamma, env, &args2, &args1) .context("Subtype fails at function input type")?; subtype_(report, gamma, env, &rets1, &rets2) @@ -232,45 +212,17 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( if f1.modes != f2.modes { return Err(Error::msg("Function mode mismatch")); } - let f1_args = f1 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f2_args = f2 - .args - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f1_rets = f1 - .rets - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let f2_rets = f2 - .rets - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let args1 = to_tuple(&f1_args); - let args2 = to_tuple(&f2_args); - let rets1 = to_tuple(&f1_rets); - let rets2 = to_tuple(&f2_rets); + let args1 = to_tuple(&f1.args); + let args2 = to_tuple(&f2.args); + let rets1 = to_tuple(&f1.rets); + let rets2 = to_tuple(&f2.rets); equal(gamma, env, &args1, &args2).context("Mismatch in function input type")?; equal(gamma, env, &rets1, &rets2).context("Mismatch in function return type")?; Ok(()) } (Class(init1, ty1), Class(init2, ty2)) => { - let init1_typ = init1 - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let init2_typ = init2 - .iter() - .map(|arg| arg.typ.clone()) - .collect::>(); - let init_1 = to_tuple(&init1_typ); - let init_2 = to_tuple(&init2_typ); + let init_1 = to_tuple(&init1); + let init_2 = to_tuple(&init2); equal(gamma, env, &init_1, &init_2).context(format!( "Mismatch in init args: {} and {}", pp_args(init1), @@ -312,13 +264,13 @@ where } } -fn to_tuple(args: &[Type]) -> Type { +fn to_tuple(args: &[ArgType]) -> Type { TypeInner::Record( args.iter() .enumerate() - .map(|(i, ty)| Field { + .map(|(i, arg)| Field { id: Label::Id(i as u32).into(), - ty: ty.clone(), + ty: arg.typ.clone(), }) .collect(), ) From c915327b1b7af01ea819dc57b51fa61a6835f921 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 9 Oct 2025 18:30:18 +0200 Subject: [PATCH 6/6] style: clippy --- rust/candid/src/types/subtype.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 234741173..5723a25f6 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -221,8 +221,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( Ok(()) } (Class(init1, ty1), Class(init2, ty2)) => { - let init_1 = to_tuple(&init1); - let init_2 = to_tuple(&init2); + let init_1 = to_tuple(init1); + let init_2 = to_tuple(init2); equal(gamma, env, &init_1, &init_2).context(format!( "Mismatch in init args: {} and {}", pp_args(init1),