From b1d118cd3d5de465a35b41fcd2a96231f8fbdb34 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 17 Jun 2025 14:40:47 +0200 Subject: [PATCH 01/34] refactor: move `IDL*` types into the candid crate, under the `parser` feature flag --- rust/candid/Cargo.toml | 3 +- rust/candid/src/types/mod.rs | 2 + .../types.rs => candid/src/types/parser.rs} | 46 +++---------------- rust/candid_parser/src/bindings/javascript.rs | 3 +- rust/candid_parser/src/bindings/motoko.rs | 4 +- rust/candid_parser/src/error.rs | 17 ++++++- rust/candid_parser/src/grammar.lalrpop | 2 +- rust/candid_parser/src/lib.rs | 36 +++++++++++---- rust/candid_parser/src/test.rs | 2 +- rust/candid_parser/src/typing.rs | 20 ++++---- rust/candid_parser/src/utils.rs | 18 ++++---- rust/candid_parser/tests/parse_type.rs | 8 ++-- rust/candid_parser/tests/parse_value.rs | 5 +- rust/candid_parser/tests/value.rs | 4 +- tools/didc/src/main.rs | 17 ++++--- 15 files changed, 100 insertions(+), 87 deletions(-) rename rust/{candid_parser/src/types.rs => candid/src/types/parser.rs} (65%) diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index d6bb9140a..0223703a4 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -45,8 +45,9 @@ candid_parser = { path = "../candid_parser" } bignum = ["dep:num-bigint", "dep:num-traits"] printer = ["dep:pretty"] value = ["bignum", "printer"] +parser = [] default = ["serde_bytes", "printer", "bignum"] -all = ["default", "value", "ic_principal/arbitrary"] +all = ["default", "value", "ic_principal/arbitrary", "parser"] [[test]] name = "types" diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 72b385c90..6bad935ba 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -8,6 +8,8 @@ use serde::ser::Error; mod impls; pub mod internal; +#[cfg(feature = "parser")] +pub mod parser; pub mod subtype; pub mod type_env; #[cfg_attr(docsrs, doc(cfg(feature = "value")))] diff --git a/rust/candid_parser/src/types.rs b/rust/candid/src/types/parser.rs similarity index 65% rename from rust/candid_parser/src/types.rs rename to rust/candid/src/types/parser.rs index 91ed6efdb..1f943ea2d 100644 --- a/rust/candid_parser/src/types.rs +++ b/rust/candid/src/types/parser.rs @@ -1,7 +1,6 @@ -use crate::Result; -use candid::types::{FuncMode, Label}; +use crate::types::{FuncMode, Label}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum IDLType { PrimT(PrimType), VarT(String), @@ -60,14 +59,14 @@ pub enum PrimType { Empty, }} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct FuncType { pub modes: Vec, pub args: Vec, pub rets: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct IDLArgType { pub typ: IDLType, pub name: Option, @@ -91,7 +90,7 @@ impl IDLArgType { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeField { pub label: Label, pub typ: IDLType, @@ -104,13 +103,13 @@ pub enum Dec { ImportServ(String), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Binding { pub id: String, pub typ: IDLType, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct IDLProg { pub decs: Vec, pub actor: Option, @@ -121,34 +120,3 @@ pub struct IDLInitArgs { pub decs: Vec, pub args: Vec, } - -impl std::str::FromStr for IDLProg { - type Err = crate::Error; - fn from_str(str: &str) -> Result { - let lexer = super::token::Tokenizer::new(str); - Ok(super::grammar::IDLProgParser::new().parse(lexer)?) - } -} -impl std::str::FromStr for IDLInitArgs { - type Err = crate::Error; - fn from_str(str: &str) -> Result { - let lexer = super::token::Tokenizer::new(str); - Ok(super::grammar::IDLInitArgsParser::new().parse(lexer)?) - } -} - -impl std::str::FromStr for IDLType { - type Err = crate::Error; - fn from_str(str: &str) -> Result { - let lexer = super::token::Tokenizer::new(str); - Ok(super::grammar::TypParser::new().parse(lexer)?) - } -} - -impl std::str::FromStr for IDLTypes { - type Err = crate::Error; - fn from_str(str: &str) -> Result { - let lexer = super::token::Tokenizer::new(str); - Ok(super::grammar::TypsParser::new().parse(lexer)?) - } -} diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index c0db60de6..b21d5f501 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -370,6 +370,7 @@ pub mod test { use super::value; use crate::test::{HostAssert, HostTest, Test}; use candid::pretty::utils::*; + use candid::types::parser::IDLProg; use candid::TypeEnv; use pretty::RcDoc; @@ -405,7 +406,7 @@ import { Principal } from './principal'; let mut env = TypeEnv::new(); crate::check_prog( &mut env, - &crate::IDLProg { + &IDLProg { decs: test.defs, actor: None, }, diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index f7ceb949a..29dac5dba 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -3,8 +3,8 @@ use candid::pretty::candid::is_valid_as_id; use candid::pretty::utils::*; -use candid::types::{ArgType, FuncMode}; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::types::{ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeInner}; +use candid::TypeEnv; use pretty::RcDoc; // The definition of tuple is language specific. diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs index 75b46e3e8..5445fcd0d 100644 --- a/rust/candid_parser/src/error.rs +++ b/rust/candid_parser/src/error.rs @@ -1,10 +1,11 @@ //! When serializing or deserializing Candid goes wrong. +use candid::types::parser::{IDLProg, IDLTypes}; use codespan_reporting::diagnostic::Label; use std::io; use thiserror::Error; -use crate::token; +use crate::{parse_idl_prog, parse_idl_types, token}; use codespan_reporting::{ diagnostic::Diagnostic, files::{Error as ReportError, SimpleFile}, @@ -115,6 +116,20 @@ where }) } +pub fn pretty_parse_idl_prog(name: &str, str: &str) -> Result { + parse_idl_prog(str).or_else(|e| { + pretty_diagnose(name, str, &e)?; + Err(e) + }) +} + +pub fn pretty_parse_idl_types(name: &str, str: &str) -> Result { + parse_idl_types(str).or_else(|e| { + pretty_diagnose(name, str, &e)?; + Err(e) + }) +} + /// Wrap the parser error and pretty print the error message. /// ``` /// use candid_parser::{pretty_wrap, parse_idl_args}; diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 3a3f3bb05..316c22de7 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -1,7 +1,7 @@ -use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType}; use super::test::{Assert, Input, Test}; use super::token::{Token, error, error2, LexicalError, Span}; use candid::{Principal, types::Label}; +use candid::types::parser::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType}; use candid::types::value::{IDLField, IDLValue, IDLArgs, VariantValue}; use candid::types::{TypeEnv, FuncMode}; use candid::utils::check_unique; diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index ca850bcdf..73fdd34e2 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -47,7 +47,7 @@ //! ``` //! # fn f() -> anyhow::Result<()> { //! use candid::{TypeEnv, types::{Type, TypeInner}}; -//! use candid_parser::{IDLProg, check_prog}; +//! use candid_parser::{check_prog, parse_idl_prog}; //! let did_file = r#" //! type List = opt record { head: int; tail: List }; //! type byte = nat8; @@ -58,7 +58,7 @@ //! "#; //! //! // Parse did file into an AST -//! let ast: IDLProg = did_file.parse()?; +//! let ast = parse_idl_prog(did_file)?; //! //! // Type checking a given .did file //! // let (env, opt_actor) = check_file("a.did")?; @@ -86,7 +86,7 @@ //! use candid::{IDLArgs, types::value::IDLValue}; //! use candid_parser::parse_idl_args; //! # use candid::TypeEnv; -//! # use candid_parser::{IDLProg, check_prog}; +//! # use candid_parser::{check_prog, parse_idl_prog}; //! # let did_file = r#" //! # type List = opt record { head: int; tail: List }; //! # type byte = nat8; @@ -95,7 +95,7 @@ //! # g : (List) -> (int) query; //! # } //! # "#; -//! # let ast = did_file.parse::()?; +//! # let ast = parse_idl_prog(did_file)?; //! # let mut env = TypeEnv::new(); //! # let actor = check_prog(&mut env, &ast)?.unwrap(); //! // Get method type f : (byte, int, nat, int8) -> (List) @@ -119,15 +119,15 @@ #![cfg_attr(docsrs, feature(doc_cfg))] pub mod error; -pub use error::{pretty_parse, pretty_wrap, Error, Result}; +pub use error::{ + pretty_parse, pretty_parse_idl_prog, pretty_parse_idl_types, pretty_wrap, Error, Result, +}; pub mod bindings; pub mod grammar; pub mod token; -pub mod types; -pub mod utils; -pub use types::IDLProg; pub mod typing; +pub mod utils; pub use typing::{check_file, check_prog, pretty_check_file}; pub use candid; @@ -142,6 +142,26 @@ pub mod configs; pub mod random; pub mod test; +pub fn parse_idl_prog(str: &str) -> Result { + let lexer = token::Tokenizer::new(str); + Ok(grammar::IDLProgParser::new().parse(lexer)?) +} + +pub fn parse_idl_init_args(str: &str) -> Result { + let lexer = token::Tokenizer::new(str); + Ok(grammar::IDLInitArgsParser::new().parse(lexer)?) +} + +pub fn parse_idl_type(str: &str) -> Result { + let lexer = token::Tokenizer::new(str); + Ok(grammar::TypParser::new().parse(lexer)?) +} + +pub fn parse_idl_types(str: &str) -> Result { + let lexer = token::Tokenizer::new(str); + Ok(grammar::TypsParser::new().parse(lexer)?) +} + pub fn parse_idl_args(s: &str) -> crate::Result { let lexer = token::Tokenizer::new(s); Ok(grammar::ArgsParser::new().parse(lexer)?) diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index 563b756a7..e75da188a 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -1,6 +1,6 @@ -use super::types::{Dec, IDLProg, IDLType}; use super::typing::check_prog; use crate::{Error, Result}; +use candid::types::parser::{Dec, IDLProg, IDLType}; use candid::types::value::IDLArgs; use candid::types::{Type, TypeEnv}; use candid::DecoderConfig; diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 3cc20367f..5b5a45a47 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,6 +1,8 @@ -use super::types::*; -use crate::{pretty_parse, Error, Result}; -use candid::types::{ArgType, Field, Function, Type, TypeEnv, TypeInner}; +use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; +use candid::types::{ + parser::{Binding, Dec, IDLArgType, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField}, + ArgType, Field, Function, Type, TypeEnv, TypeInner, +}; use candid::utils::check_unique; use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; @@ -11,7 +13,7 @@ pub struct Env<'a> { } /// Convert candid AST to internal Type -pub fn ast_to_type(env: &TypeEnv, ast: &super::types::IDLType) -> Result { +pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { let env = Env { te: &mut env.clone(), pre: false, @@ -237,9 +239,9 @@ fn load_imports( let code = std::fs::read_to_string(&path) .map_err(|_| Error::msg(format!("Cannot import {file:?}")))?; let code = if is_pretty { - pretty_parse::(path.to_str().unwrap(), &code)? + pretty_parse_idl_prog(path.to_str().unwrap(), &code)? } else { - code.parse::()? + parse_idl_prog(&code)? }; let base = path.parent().unwrap(); load_imports(is_pretty, base, visited, &code, list)?; @@ -324,9 +326,9 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> let prog = std::fs::read_to_string(file).map_err(|_| Error::msg(format!("Cannot open {file:?}")))?; let prog = if is_pretty { - pretty_parse::(file.to_str().unwrap(), &prog)? + pretty_parse_idl_prog(file.to_str().unwrap(), &prog)? } else { - prog.parse::()? + parse_idl_prog(&prog)? }; let mut visited = BTreeMap::new(); let mut imports = Vec::new(); @@ -346,7 +348,7 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> let mut actor: Option = None; for (include_serv, path, name) in imports.iter() { let code = std::fs::read_to_string(path)?; - let code = code.parse::()?; + let code = parse_idl_prog(&code)?; check_decs(&mut env, &code.decs)?; if *include_serv { let t = check_actor(&env, &code.actor)?; diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 1552d67e2..5074a87ee 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,6 +1,8 @@ -use crate::{check_prog, pretty_check_file, pretty_parse, Error, Result}; -use candid::types::TypeInner; -use candid::{types::Type, TypeEnv}; +use crate::{check_prog, pretty_check_file, pretty_parse_idl_prog, Error, Result}; +use candid::{ + types::{Type, TypeInner}, + TypeEnv, +}; use std::path::Path; pub enum CandidSource<'a> { @@ -13,7 +15,7 @@ impl CandidSource<'_> { Ok(match self { CandidSource::File(path) => pretty_check_file(path)?, CandidSource::Text(str) => { - let ast = pretty_parse("", str)?; + let ast = pretty_parse_idl_prog("", str)?; let mut env = TypeEnv::new(); let actor = check_prog(&mut env, &ast)?; (env, actor) @@ -83,7 +85,7 @@ pub fn get_metadata(env: &TypeEnv, serv: &Option) -> Option { /// Merge canister metadata candid:args and candid:service into a service constructor. /// If candid:service already contains init args, returns the original did file. pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { - use crate::{types::IDLInitArgs, typing::check_init_args}; + use crate::{parse_idl_init_args, typing::check_init_args}; use candid::types::TypeInner; let candid = CandidSource::Text(candid); let (env, serv) = candid.load()?; @@ -92,7 +94,7 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { match serv.as_ref() { TypeInner::Class(_, _) => Ok((env, serv)), TypeInner::Service(_) => { - let prog = init.parse::()?; + let prog = parse_idl_init_args(init)?; let mut env2 = TypeEnv::new(); let args = check_init_args(&mut env2, &env, &prog)?; Ok((env2, TypeInner::Class(args, serv).into())) @@ -104,9 +106,9 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { /// Note that this only checks structural equality, not equivalence. For recursive types, it may reject /// an unrolled type. pub fn check_rust_type(candid_args: &str) -> Result<()> { - use crate::{types::IDLInitArgs, typing::check_init_args}; + use crate::{parse_idl_init_args, typing::check_init_args}; use candid::types::{internal::TypeContainer, subtype::equal, TypeEnv}; - let parsed = candid_args.parse::()?; + let parsed = parse_idl_init_args(candid_args)?; let mut env = TypeEnv::new(); let args = check_init_args(&mut env, &TypeEnv::new(), &parsed)?; let mut rust_env = TypeContainer::new(); diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index ad9569404..3129c4afd 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -2,14 +2,14 @@ use candid::pretty::candid::compile; use candid::types::TypeEnv; use candid_parser::bindings::{javascript, motoko, rust, typescript}; use candid_parser::configs::Configs; -use candid_parser::types::IDLProg; +use candid_parser::parse_idl_prog; use candid_parser::typing::{check_file, check_prog}; use goldenfile::Mint; use std::io::Write; use std::path::Path; #[test] -fn parse_idl_prog() { +fn test_parse_idl_prog() { let prog = r#" import "test.did"; type my_type = principal; @@ -28,7 +28,7 @@ service server : { i : f; } "#; - prog.parse::().unwrap(); + parse_idl_prog(prog).unwrap(); } #[test_generator::test_resources("rust/candid_parser/tests/assets/*.did")] @@ -45,7 +45,7 @@ fn compiler_test(resource: &str) { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); let content = compile(&env, &actor); // Type check output - let ast = content.parse::().unwrap(); + let ast = parse_idl_prog(&content).unwrap(); check_prog(&mut TypeEnv::new(), &ast).unwrap(); writeln!(output, "{content}").unwrap(); } diff --git a/rust/candid_parser/tests/parse_value.rs b/rust/candid_parser/tests/parse_value.rs index 65c23769b..c93f5d587 100644 --- a/rust/candid_parser/tests/parse_value.rs +++ b/rust/candid_parser/tests/parse_value.rs @@ -1,7 +1,7 @@ use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, Type, TypeEnv, TypeInner}; use candid::{record, variant, CandidType, Nat}; -use candid_parser::parse_idl_args; +use candid_parser::{parse_idl_args, parse_idl_type}; fn parse_args(input: &str) -> IDLArgs { parse_idl_args(input).unwrap() @@ -12,9 +12,8 @@ fn parse_args_err(input: &str) -> candid_parser::Result { } fn parse_type(input: &str) -> Type { - use candid_parser::types::IDLType; let env = TypeEnv::new(); - let ast = input.parse::().unwrap(); + let ast = parse_idl_type(input).unwrap(); candid_parser::typing::ast_to_type(&env, &ast).unwrap() } diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index e23a579a4..aa6e0a281 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -1,7 +1,7 @@ use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, TypeEnv}; use candid::{decode_args, decode_one, Decode}; -use candid_parser::{parse_idl_args, types::IDLProg, typing::check_prog}; +use candid_parser::{parse_idl_args, parse_idl_prog, typing::check_prog}; #[test] fn test_parser() { @@ -31,7 +31,7 @@ service : { f : f; } "#; - let ast = candid.parse::().unwrap(); + let ast = parse_idl_prog(candid).unwrap(); let mut env = TypeEnv::new(); let actor = check_prog(&mut env, &ast).unwrap().unwrap(); let method = env.get_method(&actor, "f").unwrap(); diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 26e0836ab..0447dc31e 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,11 +1,12 @@ use anyhow::{bail, Result}; -use candid_parser::candid::types::{subtype, Type}; +use candid_parser::candid::types::{ + parser::{IDLType, IDLTypes}, + subtype, Type, +}; use candid_parser::{ - configs::Configs, - parse_idl_args, parse_idl_value, pretty_check_file, pretty_parse, pretty_wrap, - types::{IDLType, IDLTypes}, - typing::ast_to_type, - Error, IDLArgs, IDLValue, TypeEnv, + configs::Configs, parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, + pretty_parse, pretty_parse_idl_types, pretty_wrap, typing::ast_to_type, Error, IDLArgs, + IDLValue, TypeEnv, }; use clap::Parser; use console::style; @@ -98,7 +99,9 @@ enum Command { Subtype { #[clap(short, long)] defs: Option, + #[clap(value_parser = parse_idl_type)] ty1: IDLType, + #[clap(value_parser = parse_idl_type)] ty2: IDLType, }, } @@ -160,7 +163,7 @@ fn parse_args(str: &str) -> Result { pretty_wrap("candid arguments", str, parse_idl_args) } fn parse_types(str: &str) -> Result { - pretty_parse("type annotations", str) + pretty_parse_idl_types("type annotations", str) } fn load_config(input: &Option) -> Result { match input { From 6f0fd7805526cb048b555b171ad682772eb7b11e Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 17 Jun 2025 16:37:12 +0200 Subject: [PATCH 02/34] chore: update changelog and docs --- Changelog.md | 33 ++++++++++++++++++++++++++++++++- rust/candid_parser/src/error.rs | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index e901a0513..78e8c4646 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,11 +6,42 @@ ### Candid * Breaking changes: - + `pp_args` and `pp_init_args` noew require a `&[ArgType]` parameter. The `pp_rets` function has been added, with the signature of the old `pp_args`. + + `pp_args` and `pp_init_args` now require a `&[ArgType]` parameter. The `pp_rets` function has been added, with the signature of the old `pp_args`. + +* Non-breaking changes: + + The following structs have been moved from the `candid_parser` crate to the `candid::types::parser` module, under the `parser` feature flag: + - `IDLType` + - `IDLTypes` + - `PrimType` + - `FuncType` + - `IDLArgType` + - `TypeField` + - `Dec` + - `Binding` + - `IDLProg` + - `IDLInitArgs` ### candid_parser * Breaking changes: + + The following structs have been moved to the `candid` crate: + - `IDLType` + - `IDLTypes` + - `PrimType` + - `FuncType` + - `IDLArgType` + - `TypeField` + - `Dec` + - `Binding` + - `IDLProg` + - `IDLInitArgs` + As a consequence, the `FromStr` trait is no longer implemented for the following types: + - `IDLProg` + - `IDLInitArgs` + - `IDLType` + - `IDLTypes` + You must now use the `parse_idl_prog`, `parse_idl_init_args`, `parse_idl_type` and `parse_idl_types` functions to parse these types, respectively. + + `pretty_parse` doesn't work anymore with the `IDLProg` and `IDLTypes` types. Use `pretty_parse_idl_prog` and `pretty_parse_idl_types` instead. + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. * Non-breaking changes: diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs index 5445fcd0d..b4e3b4dfe 100644 --- a/rust/candid_parser/src/error.rs +++ b/rust/candid_parser/src/error.rs @@ -106,6 +106,7 @@ impl From for Error { } } +/// Does not work for parsing [IDLProg] and [IDLTypes], use [pretty_parse_idl_prog] and [pretty_parse_idl_types] instead. pub fn pretty_parse(name: &str, str: &str) -> Result where T: std::str::FromStr, From a9aee1ca97281d5d3abe8b0f2a6d26564a181305 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 17 Jun 2025 16:47:16 +0200 Subject: [PATCH 03/34] refactor: map pretty error --- rust/candid_parser/src/error.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs index b4e3b4dfe..7b35942e9 100644 --- a/rust/candid_parser/src/error.rs +++ b/rust/candid_parser/src/error.rs @@ -111,24 +111,15 @@ pub fn pretty_parse(name: &str, str: &str) -> Result where T: std::str::FromStr, { - str.parse::().or_else(|e| { - pretty_diagnose(name, str, &e)?; - Err(e) - }) + str.parse::().or_else(|e| pretty_print_err(name, str, e)) } pub fn pretty_parse_idl_prog(name: &str, str: &str) -> Result { - parse_idl_prog(str).or_else(|e| { - pretty_diagnose(name, str, &e)?; - Err(e) - }) + parse_idl_prog(str).or_else(|e| pretty_print_err(name, str, e)) } pub fn pretty_parse_idl_types(name: &str, str: &str) -> Result { - parse_idl_types(str).or_else(|e| { - pretty_diagnose(name, str, &e)?; - Err(e) - }) + parse_idl_types(str).or_else(|e| pretty_print_err(name, str, e)) } /// Wrap the parser error and pretty print the error message. @@ -138,10 +129,12 @@ pub fn pretty_parse_idl_types(name: &str, str: &str) -> Result { /// # Ok::<(), candid_parser::Error>(()) /// ``` pub fn pretty_wrap(name: &str, str: &str, f: impl FnOnce(&str) -> Result) -> Result { - f(str).or_else(|e| { - pretty_diagnose(name, str, &e)?; - Err(e) - }) + f(str).or_else(|e| pretty_print_err(name, str, e)) +} + +fn pretty_print_err(name: &str, source: &str, e: Error) -> Result { + pretty_diagnose(name, source, &e)?; + Err(e) } pub fn pretty_diagnose(file_name: &str, source: &str, e: &Error) -> Result<()> { From 74f84c9f30568f8d08b84be0ecc2eb34b4ef5df3 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 18 Jun 2025 09:24:24 +0200 Subject: [PATCH 04/34] refactor: rename parser to syntax --- rust/candid/Cargo.toml | 4 ++-- rust/candid/src/types/mod.rs | 4 ++-- rust/candid/src/types/{parser.rs => syntax.rs} | 0 rust/candid_parser/src/bindings/javascript.rs | 2 +- rust/candid_parser/src/error.rs | 2 +- rust/candid_parser/src/grammar.lalrpop | 2 +- rust/candid_parser/src/lib.rs | 8 ++++---- rust/candid_parser/src/test.rs | 2 +- rust/candid_parser/src/typing.rs | 2 +- tools/didc/src/main.rs | 5 +++-- 10 files changed, 16 insertions(+), 15 deletions(-) rename rust/candid/src/types/{parser.rs => syntax.rs} (100%) diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 0223703a4..88c405446 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -45,9 +45,9 @@ candid_parser = { path = "../candid_parser" } bignum = ["dep:num-bigint", "dep:num-traits"] printer = ["dep:pretty"] value = ["bignum", "printer"] -parser = [] +syntax = [] default = ["serde_bytes", "printer", "bignum"] -all = ["default", "value", "ic_principal/arbitrary", "parser"] +all = ["default", "value", "ic_principal/arbitrary", "syntax"] [[test]] name = "types" diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 6bad935ba..92df03eb0 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -8,9 +8,9 @@ use serde::ser::Error; mod impls; pub mod internal; -#[cfg(feature = "parser")] -pub mod parser; pub mod subtype; +#[cfg(feature = "syntax")] +pub mod syntax; pub mod type_env; #[cfg_attr(docsrs, doc(cfg(feature = "value")))] #[cfg(feature = "value")] diff --git a/rust/candid/src/types/parser.rs b/rust/candid/src/types/syntax.rs similarity index 100% rename from rust/candid/src/types/parser.rs rename to rust/candid/src/types/syntax.rs diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index b21d5f501..11b54cd96 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -370,7 +370,7 @@ pub mod test { use super::value; use crate::test::{HostAssert, HostTest, Test}; use candid::pretty::utils::*; - use candid::types::parser::IDLProg; + use candid::types::syntax::IDLProg; use candid::TypeEnv; use pretty::RcDoc; diff --git a/rust/candid_parser/src/error.rs b/rust/candid_parser/src/error.rs index 7b35942e9..8ebd53cf3 100644 --- a/rust/candid_parser/src/error.rs +++ b/rust/candid_parser/src/error.rs @@ -1,6 +1,6 @@ //! When serializing or deserializing Candid goes wrong. -use candid::types::parser::{IDLProg, IDLTypes}; +use candid::types::syntax::{IDLProg, IDLTypes}; use codespan_reporting::diagnostic::Label; use std::io; use thiserror::Error; diff --git a/rust/candid_parser/src/grammar.lalrpop b/rust/candid_parser/src/grammar.lalrpop index 316c22de7..28abc6394 100644 --- a/rust/candid_parser/src/grammar.lalrpop +++ b/rust/candid_parser/src/grammar.lalrpop @@ -1,7 +1,7 @@ use super::test::{Assert, Input, Test}; use super::token::{Token, error, error2, LexicalError, Span}; use candid::{Principal, types::Label}; -use candid::types::parser::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType}; +use candid::types::syntax::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs, IDLArgType}; use candid::types::value::{IDLField, IDLValue, IDLArgs, VariantValue}; use candid::types::{TypeEnv, FuncMode}; use candid::utils::check_unique; diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 73fdd34e2..4086ac3c8 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -142,22 +142,22 @@ pub mod configs; pub mod random; pub mod test; -pub fn parse_idl_prog(str: &str) -> Result { +pub fn parse_idl_prog(str: &str) -> Result { let lexer = token::Tokenizer::new(str); Ok(grammar::IDLProgParser::new().parse(lexer)?) } -pub fn parse_idl_init_args(str: &str) -> Result { +pub fn parse_idl_init_args(str: &str) -> Result { let lexer = token::Tokenizer::new(str); Ok(grammar::IDLInitArgsParser::new().parse(lexer)?) } -pub fn parse_idl_type(str: &str) -> Result { +pub fn parse_idl_type(str: &str) -> Result { let lexer = token::Tokenizer::new(str); Ok(grammar::TypParser::new().parse(lexer)?) } -pub fn parse_idl_types(str: &str) -> Result { +pub fn parse_idl_types(str: &str) -> Result { let lexer = token::Tokenizer::new(str); Ok(grammar::TypsParser::new().parse(lexer)?) } diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index e75da188a..9f3dbd970 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -1,6 +1,6 @@ use super::typing::check_prog; use crate::{Error, Result}; -use candid::types::parser::{Dec, IDLProg, IDLType}; +use candid::types::syntax::{Dec, IDLProg, IDLType}; use candid::types::value::IDLArgs; use candid::types::{Type, TypeEnv}; use candid::DecoderConfig; diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 5b5a45a47..a4a7d7daf 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,6 +1,6 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ - parser::{Binding, Dec, IDLArgType, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField}, + syntax::{Binding, Dec, IDLArgType, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField}, ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 0447dc31e..49d16928e 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,7 +1,8 @@ use anyhow::{bail, Result}; use candid_parser::candid::types::{ - parser::{IDLType, IDLTypes}, - subtype, Type, + subtype, + syntax::{IDLType, IDLTypes}, + Type, }; use candid_parser::{ configs::Configs, parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, From d1bf39109512d7d0628ce8c70f8e69bae725eaaf Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 18 Jun 2025 09:30:26 +0200 Subject: [PATCH 05/34] refactor: use `IDL*` structs in parser --- rust/candid_parser/src/bindings/motoko.rs | 115 ++++++++++++---------- rust/candid_parser/src/typing.rs | 8 +- rust/candid_parser/src/utils.rs | 18 ++-- rust/candid_parser/tests/parse_type.rs | 6 +- tools/didc/src/main.rs | 18 ++-- 5 files changed, 89 insertions(+), 76 deletions(-) diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 29dac5dba..e70f20ab0 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -3,19 +3,21 @@ use candid::pretty::candid::is_valid_as_id; use candid::pretty::utils::*; -use candid::types::{ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeInner}; -use candid::TypeEnv; +use candid::types::{ + syntax::{Binding, Dec, FuncType, IDLArgType, IDLProg, IDLType, PrimType, TypeField}, + FuncMode, Label, +}; use pretty::RcDoc; // The definition of tuple is language specific. -fn is_tuple(t: &Type) -> bool { - match t.as_ref() { - TypeInner::Record(ref fs) => { +fn is_tuple(t: &IDLType) -> bool { + match t { + IDLType::RecordT(ref fs) => { if fs.len() <= 1 { return false; } for (i, field) in fs.iter().enumerate() { - if field.id.get_id() != (i as u32) { + if field.label.get_id() != (i as u32) { return false; } } @@ -92,9 +94,9 @@ fn escape(id: &str, is_method: bool) -> RcDoc { } } -fn pp_ty(ty: &Type) -> RcDoc { - use TypeInner::*; - match ty.as_ref() { +fn pp_prim_ty(prim: &PrimType) -> RcDoc { + use PrimType::*; + match prim { Null => str("Null"), Bool => str("Bool"), Nat => str("Nat"), @@ -112,21 +114,28 @@ fn pp_ty(ty: &Type) -> RcDoc { Text => str("Text"), Reserved => str("Any"), Empty => str("None"), - Var(ref s) => escape(s, false), - Principal => str("Principal"), - Opt(ref t) => str("?").append(pp_ty(t)), - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("Blob"), - Vec(ref t) => enclose("[", pp_ty(t), "]"), - Record(ref fs) => { + } +} + +fn pp_ty(ty: &IDLType) -> RcDoc { + use IDLType::*; + match ty { + PrimT(prim) => pp_prim_ty(prim), + VarT(ref s) => escape(s, false), + PrincipalT => str("Principal"), + OptT(ref t) => str("?").append(pp_ty(t)), + VecT(ref t) if matches!(t.as_ref(), PrimT(PrimType::Nat8)) => str("Blob"), + VecT(ref t) => enclose("[", pp_ty(t), "]"), + RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ","); enclose("(", tuple, ")") } else { let fields = concat(fs.iter().map(pp_field), ";"); enclose_space("{", fields, "}") } } - Variant(ref fs) => { + VariantT(ref fs) => { if fs.is_empty() { str("{#}") } else { @@ -134,22 +143,21 @@ fn pp_ty(ty: &Type) -> RcDoc { enclose_space("{", fields, "}") } } - Func(ref func) => pp_function(func), - Service(ref serv) => pp_service(serv), - Class(ref args, ref t) => { + FuncT(ref func) => pp_function(func), + ServT(ref serv) => pp_service(serv), + ClassT(ref args, ref t) => { let doc = pp_args(args).append(" -> async "); match t.as_ref() { - Service(ref serv) => doc.append(pp_service(serv)), - Var(ref s) => doc.append(s), + IDLType::ServT(ref serv) => doc.append(pp_service(serv)), + IDLType::VarT(ref s) => doc.append(s), _ => unreachable!(), } } - Knot(_) | Unknown | Future => unreachable!(), } } -fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(str) => escape(str, false), Label::Id(n) | Label::Unnamed(n) => str("_") .append(RcDoc::as_string(n)) @@ -158,19 +166,21 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field(field: &Field) -> RcDoc { - pp_label(&field.id).append(" : ").append(pp_ty(&field.ty)) +fn pp_field(field: &TypeField) -> RcDoc { + pp_label(&field.label) + .append(" : ") + .append(pp_ty(&field.typ)) } -fn pp_variant(field: &Field) -> RcDoc { - let doc = str("#").append(pp_label(&field.id)); - if *field.ty != TypeInner::Null { - doc.append(" : ").append(pp_ty(&field.ty)) +fn pp_variant(field: &TypeField) -> RcDoc { + let doc = str("#").append(pp_label(&field.label)); + if field.typ != IDLType::PrimT(PrimType::Null) { + doc.append(" : ").append(pp_ty(&field.typ)) } else { doc } } -fn pp_function(func: &Function) -> RcDoc { +fn pp_function(func: &FuncType) -> RcDoc { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); match func.modes.as_slice() { @@ -194,7 +204,7 @@ fn pp_function(func: &Function) -> RcDoc { } .nest(INDENT_SPACE) } -fn pp_args(args: &[ArgType]) -> RcDoc { +fn pp_args(args: &[IDLArgType]) -> RcDoc { match args { [ty] => { let typ = if is_tuple(&ty.typ) { @@ -222,7 +232,7 @@ fn pp_args(args: &[ArgType]) -> RcDoc { } } -fn pp_rets(args: &[Type]) -> RcDoc { +fn pp_rets(args: &[IDLType]) -> RcDoc { match args { [ty] => { if is_tuple(ty) { @@ -238,41 +248,44 @@ fn pp_rets(args: &[Type]) -> RcDoc { } } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[Binding]) -> RcDoc { let doc = concat( serv.iter() - .map(|(id, func)| escape(id, true).append(" : ").append(pp_ty(func))), + .map(|b| escape(&b.id, true).append(" : ").append(pp_ty(&b.typ))), ";", ); kwd("actor").append(enclose_space("{", doc, "}")) } -fn pp_defs(env: &TypeEnv) -> RcDoc { - lines(env.0.iter().map(|(id, ty)| { - kwd("public type") - .append(escape(id, false)) - .append(" = ") - .append(pp_ty(ty)) - .append(";") +fn pp_defs(decs: &[Dec]) -> RcDoc { + lines(decs.iter().map(|dec| { + match dec { + Dec::TypD(binding) => kwd("public type") + .append(escape(&binding.id, false)) + .append(" = ") + .append(pp_ty(&binding.typ)) + .append(";"), + _ => unreachable!(), + } })) } -fn pp_actor(ty: &Type) -> RcDoc { - match ty.as_ref() { - TypeInner::Service(ref serv) => pp_service(serv), - TypeInner::Var(_) | TypeInner::Class(_, _) => pp_ty(ty), +fn pp_actor(ty: &IDLType) -> RcDoc { + match ty { + IDLType::ServT(ref serv) => pp_service(serv), + IDLType::VarT(_) | IDLType::ClassT(_, _) => pp_ty(ty), _ => unreachable!(), } } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { +pub fn compile(prog: &IDLProg) -> String { let header = r#"// This is a generated Motoko binding. // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. "#; - let doc = match actor { - None => pp_defs(env), + let doc = match &prog.actor { + None => pp_defs(&prog.decs), Some(actor) => { - let defs = pp_defs(env); + let defs = pp_defs(&prog.decs); let actor = kwd("public type Self =").append(pp_actor(actor)); defs.append(actor) } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index a4a7d7daf..63dcf6171 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -313,7 +313,7 @@ fn merge_actor( } } -fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> { +fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLProg, Option)> { let base = if file.is_absolute() { file.parent().unwrap().to_path_buf() } else { @@ -360,13 +360,13 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option)> if actor.is_some() { res = merge_actor(&env, &res, &actor, "")?; } - Ok((te, res)) + Ok((te, prog, res)) } /// Type check did file including the imports. -pub fn check_file(file: &Path) -> Result<(TypeEnv, Option)> { +pub fn check_file(file: &Path) -> Result<(TypeEnv, IDLProg, Option)> { check_file_(file, false) } -pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, Option)> { +pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, IDLProg, Option)> { check_file_(file, true) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 5074a87ee..46ce8ede5 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,6 +1,6 @@ use crate::{check_prog, pretty_check_file, pretty_parse_idl_prog, Error, Result}; use candid::{ - types::{Type, TypeInner}, + types::{syntax::IDLProg, Type, TypeInner}, TypeEnv, }; use std::path::Path; @@ -11,14 +11,14 @@ pub enum CandidSource<'a> { } impl CandidSource<'_> { - pub fn load(&self) -> Result<(TypeEnv, Option)> { + pub fn load(&self) -> Result<(TypeEnv, IDLProg, Option)> { Ok(match self { CandidSource::File(path) => pretty_check_file(path)?, CandidSource::Text(str) => { let ast = pretty_parse_idl_prog("", str)?; let mut env = TypeEnv::new(); let actor = check_prog(&mut env, &ast)?; - (env, actor) + (env, ast, actor) } }) } @@ -26,9 +26,9 @@ impl CandidSource<'_> { /// Check compatibility of two service types pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { - let (mut env, t1) = new.load()?; + let (mut env, _, t1) = new.load()?; let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; - let (env2, t2) = old.load()?; + let (env2, _, t2) = old.load()?; let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); @@ -38,9 +38,9 @@ pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { /// Check structural equality of two service types pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { - let (mut env, t1) = left.load()?; + let (mut env, _, t1) = left.load()?; let t1 = t1.ok_or_else(|| Error::msg("left interface has no main service type"))?; - let (env2, t2) = right.load()?; + let (env2, _, t2) = right.load()?; let t2 = t2.ok_or_else(|| Error::msg("right interface has no main service type"))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); @@ -52,7 +52,7 @@ pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { /// If the original did file contains imports, the output flattens the type definitions. /// For now, the comments from the original did file is omitted. pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, Type))> { - let (env, serv) = candid.load()?; + let (env, _, serv) = candid.load()?; let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; let serv = env.trace_type(&serv)?; Ok(match serv.as_ref() { @@ -88,7 +88,7 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { use crate::{parse_idl_init_args, typing::check_init_args}; use candid::types::TypeInner; let candid = CandidSource::Text(candid); - let (env, serv) = candid.load()?; + let (env, _, serv) = candid.load()?; let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; let serv = env.trace_type(&serv)?; match serv.as_ref() { diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 3129c4afd..96bb80226 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -40,7 +40,7 @@ fn compiler_test(resource: &str) { let candid_path = base_path.join(filename); match check_file(&candid_path) { - Ok((env, actor)) => { + Ok((env, prog, actor)) => { { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); let content = compile(&env, &actor); @@ -52,12 +52,12 @@ fn compiler_test(resource: &str) { { match filename.file_name().unwrap().to_str().unwrap() { "unicode.did" | "escape.did" => { - check_error(|| motoko::compile(&env, &actor), "not a valid Motoko id") + check_error(|| motoko::compile(&prog), "not a valid Motoko id") } _ => { let mut output = mint.new_goldenfile(filename.with_extension("mo")).unwrap(); - let content = motoko::compile(&env, &actor); + let content = motoko::compile(&prog); writeln!(output, "{content}").unwrap(); } } diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 49d16928e..ff0c2742f 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Result}; use candid_parser::candid::types::{ subtype, - syntax::{IDLType, IDLTypes}, + syntax::{IDLProg, IDLType, IDLTypes}, Type, }; use candid_parser::{ @@ -131,10 +131,10 @@ impl TypeAnnotation { self.tys.is_none() && self.method.is_none() } fn get_types(&self, mode: Mode) -> candid_parser::Result<(TypeEnv, Vec)> { - let (env, actor) = if let Some(ref file) = self.defs { + let (env, _, actor) = if let Some(ref file) = self.defs { pretty_check_file(file)? } else { - (TypeEnv::new(), None) + (TypeEnv::new(), IDLProg::default(), None) }; match (&self.tys, &self.method) { (None, None) => Err(Error::msg("no type annotations")), @@ -190,9 +190,9 @@ fn main() -> Result<()> { previous, strict, } => { - let (mut env, opt_t1) = pretty_check_file(&input)?; + let (mut env, _, opt_t1) = pretty_check_file(&input)?; if let Some(previous) = previous { - let (env2, opt_t2) = pretty_check_file(&previous)?; + let (env2, _, opt_t2) = pretty_check_file(&previous)?; match (opt_t1, opt_t2) { (Some(t1), Some(t2)) => { let mut gamma = HashSet::new(); @@ -210,10 +210,10 @@ fn main() -> Result<()> { } } Command::Subtype { defs, ty1, ty2 } => { - let (env, _) = if let Some(file) = defs { + let (env, _, _) = if let Some(file) = defs { pretty_check_file(&file)? } else { - (TypeEnv::new(), None) + (TypeEnv::new(), IDLProg::default(), None) }; let ty1 = ast_to_type(&env, &ty1)?; let ty2 = ast_to_type(&env, &ty2)?; @@ -226,7 +226,7 @@ fn main() -> Result<()> { methods, } => { let configs = load_config(&config)?; - let (env, mut actor) = pretty_check_file(&input)?; + let (env, prog, mut actor) = pretty_check_file(&input)?; if !methods.is_empty() { actor = Some(candid_parser::bindings::analysis::project_methods( &env, &actor, methods, @@ -236,7 +236,7 @@ fn main() -> Result<()> { "js" => candid_parser::bindings::javascript::compile(&env, &actor), "ts" => candid_parser::bindings::typescript::compile(&env, &actor), "did" => candid_parser::pretty::candid::compile(&env, &actor), - "mo" => candid_parser::bindings::motoko::compile(&env, &actor), + "mo" => candid_parser::bindings::motoko::compile(&prog), "rs" => { use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; let external = configs From eb7c372c27cc28b8542502b563b91dc39e873dfd Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 18 Jun 2025 16:36:33 +0200 Subject: [PATCH 06/34] refactor: use IDLTypes in parser (motoko only) --- rust/candid/src/types/syntax.rs | 69 +++++++++- rust/candid_parser/src/bindings/motoko.rs | 27 ++-- rust/candid_parser/src/typing.rs | 123 ++++++++++++------ rust/candid_parser/src/utils.rs | 14 +- rust/candid_parser/tests/assets/ok/class.mo | 2 +- rust/candid_parser/tests/assets/ok/cyclic.mo | 2 +- rust/candid_parser/tests/assets/ok/example.mo | 36 ++--- .../candid_parser/tests/assets/ok/fieldnat.mo | 2 +- rust/candid_parser/tests/assets/ok/keyword.mo | 8 +- .../tests/assets/ok/management.mo | 42 +++--- .../tests/assets/ok/recursion.mo | 8 +- rust/candid_parser/tests/assets/ok/service.mo | 2 +- rust/candid_parser/tests/parse_type.rs | 6 +- tools/didc/src/main.rs | 15 ++- 14 files changed, 233 insertions(+), 123 deletions(-) diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 1f943ea2d..208479ab3 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + use crate::types::{FuncMode, Label}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -109,7 +111,7 @@ pub struct Binding { pub typ: IDLType, } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct IDLProg { pub decs: Vec, pub actor: Option, @@ -120,3 +122,68 @@ pub struct IDLInitArgs { pub decs: Vec, pub args: Vec, } + +#[derive(Debug, Default)] +pub struct IDLEnv { + pub types_bindings: Vec, + types_bindings_ids: BTreeSet, + pub actor: Option, +} + +impl From<&IDLProg> for IDLEnv { + fn from(prog: &IDLProg) -> Self { + let mut types_bindings_ids = BTreeSet::new(); + let mut types_bindings = Vec::new(); + + for dec in prog.decs.iter() { + if let Dec::TypD(binding) = dec { + let is_duplicate = types_bindings_ids.insert(binding.id.clone()); + if !is_duplicate { + types_bindings.push(binding.clone()); + } + } + } + + let mut env = Self { + types_bindings, + types_bindings_ids, + actor: None, + }; + env.set_actor(prog.actor.clone()); + env + } +} + +impl IDLEnv { + pub fn new() -> Self { + Self::default() + } + + pub fn insert_binding(&mut self, binding: Binding) { + let is_duplicate = self.types_bindings_ids.insert(binding.id.clone()); + if !is_duplicate { + self.types_bindings.push(binding); + } + } + + pub fn set_actor(&mut self, actor: Option) { + self.actor = actor; + } + + pub fn find_type(&self, id: &str) -> Result<&IDLType, String> { + self.types_bindings + .iter() + .find(|b| b.id == id) + .map(|b| &b.typ) + .ok_or(format!("Unbound type identifier: {id}")) + } + + pub fn as_service<'a>(&'a self, t: &'a IDLType) -> Result<&'a Vec, String> { + match t { + IDLType::ServT(methods) => Ok(methods), + IDLType::VarT(id) => self.as_service(self.find_type(id)?), + IDLType::ClassT(_, t) => self.as_service(t), + _ => Err(format!("not a service type: {:?}", t)), + } + } +} diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index e70f20ab0..28a6d74a4 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -4,7 +4,7 @@ use candid::pretty::candid::is_valid_as_id; use candid::pretty::utils::*; use candid::types::{ - syntax::{Binding, Dec, FuncType, IDLArgType, IDLProg, IDLType, PrimType, TypeField}, + syntax::{Binding, FuncType, IDLArgType, IDLEnv, IDLType, PrimType, TypeField}, FuncMode, Label, }; use pretty::RcDoc; @@ -257,16 +257,13 @@ fn pp_service(serv: &[Binding]) -> RcDoc { kwd("actor").append(enclose_space("{", doc, "}")) } -fn pp_defs(decs: &[Dec]) -> RcDoc { - lines(decs.iter().map(|dec| { - match dec { - Dec::TypD(binding) => kwd("public type") - .append(escape(&binding.id, false)) - .append(" = ") - .append(pp_ty(&binding.typ)) - .append(";"), - _ => unreachable!(), - } +fn pp_defs(bindings: &[Binding]) -> RcDoc { + lines(bindings.iter().map(|binding| { + kwd("public type") + .append(escape(&binding.id, false)) + .append(" = ") + .append(pp_ty(&binding.typ)) + .append(";") })) } @@ -278,14 +275,14 @@ fn pp_actor(ty: &IDLType) -> RcDoc { } } -pub fn compile(prog: &IDLProg) -> String { +pub fn compile(env: &IDLEnv) -> String { let header = r#"// This is a generated Motoko binding. // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. "#; - let doc = match &prog.actor { - None => pp_defs(&prog.decs), + let doc = match &env.actor { + None => pp_defs(&env.types_bindings), Some(actor) => { - let defs = pp_defs(&prog.decs); + let defs = pp_defs(&env.types_bindings); let actor = kwd("public type Self =").append(pp_actor(actor)); defs.append(actor) } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 63dcf6171..0caca7dce 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,6 +1,8 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ - syntax::{Binding, Dec, IDLArgType, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField}, + syntax::{ + Binding, Dec, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField, + }, ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; @@ -9,13 +11,27 @@ use std::path::{Path, PathBuf}; pub struct Env<'a> { pub te: &'a mut TypeEnv, + pub idl_env: &'a mut IDLEnv, pub pre: bool, } +impl Env<'_> { + fn insert(&mut self, id: String, t: Type) -> bool { + let duplicate = self.te.0.insert(id, t); + duplicate.is_some() + } + + fn insert_binding(&mut self, binding: Binding, t: Type) { + self.insert(binding.id.to_string(), t); + self.idl_env.insert_binding(binding); + } +} + /// Convert candid AST to internal Type pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { let env = Env { te: &mut env.clone(), + idl_env: &mut IDLEnv::new(), pre: false, }; check_type(&env, ast) @@ -141,9 +157,9 @@ 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)?; - env.te.0.insert(id.to_string(), t); + Dec::TypD(binding) => { + let t = check_type(env, &binding.typ)?; + env.insert_binding(binding.clone(), t); } Dec::ImportType(_) | Dec::ImportServ(_) => (), } @@ -177,8 +193,8 @@ 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 { - let duplicate = env.te.0.insert(id.to_string(), TypeInner::Unknown.into()); - if duplicate.is_some() { + let is_duplicate = env.insert(id.to_string(), TypeInner::Unknown.into()); + if is_duplicate { return Err(Error::msg(format!("duplicate binding for {id}"))); } } @@ -191,22 +207,24 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { Ok(()) } -fn check_actor(env: &Env, actor: &Option) -> Result> { +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)?); - } - let serv = check_type(env, t)?; - env.te.as_service(&serv)?; - Ok(Some(TypeInner::Class(args, serv).into())) - } Some(typ) => { - let t = check_type(env, typ)?; - env.te.as_service(&t)?; - Ok(Some(t)) + let serv = if let IDLType::ClassT(ts, t) = typ { + let mut args = Vec::new(); + for arg in ts.iter() { + args.push(check_arg(env, arg)?); + } + let t = check_type(env, t)?; + env.te.as_service(&t)?; + TypeInner::Class(args, t).into() + } else { + let serv = check_type(env, typ)?; + env.te.as_service(&serv)?; + serv + }; + Ok(Some((serv, typ.clone()))) } } } @@ -256,18 +274,27 @@ fn load_imports( /// Type check IDLProg and adds bindings to type environment. Returns /// the main actor if present. This function ignores the imports. pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result> { - let mut env = Env { te, pre: false }; + let mut env = Env { + te, + idl_env: &mut IDLEnv::new(), + pre: false, + }; check_decs(&mut env, &prog.decs)?; - check_actor(&env, &prog.actor) + check_actor(&env, &prog.actor).map(|t| t.map(|t| t.0)) } /// Type check init args extracted from canister metadata candid:args. /// Need to provide `main_env`, because init args may refer to variables from the main did file. pub fn check_init_args( te: &mut TypeEnv, main_env: &TypeEnv, + idl_env: &mut IDLEnv, prog: &IDLInitArgs, ) -> Result> { - let mut env = Env { te, pre: false }; + let mut env = Env { + te, + idl_env, + pre: false, + }; check_decs(&mut env, &prog.decs)?; env.te.merge(main_env)?; let mut args = Vec::new(); @@ -279,32 +306,36 @@ pub fn check_init_args( fn merge_actor( env: &Env, - actor: &Option, - imported: &Option, + actor: &Option<(Type, IDLType)>, + imported: &Option<(Type, IDLType)>, file: &str, -) -> Result> { +) -> Result> { match imported { None => Err(Error::msg(format!( "Imported service file {file:?} has no main service" ))), - Some(t) => { + Some((t, idl_type)) => { let t = env.te.trace_type(t)?; + let idl_meths = env.idl_env.as_service(idl_type).map_err(Error::msg)?; match t.as_ref() { TypeInner::Class(_, _) => Err(Error::msg(format!( "Imported service file {file:?} has a service constructor" ))), TypeInner::Service(meths) => match actor { - None => Ok(Some(t)), - Some(t) => { + None => Ok(Some((t, idl_type.clone()))), + Some((t, idl_type)) => { let t = env.te.trace_type(t)?; let serv = env.te.as_service(&t)?; + let idl_serv = env.idl_env.as_service(idl_type).map_err(Error::msg)?; let mut ms: Vec<_> = serv.iter().chain(meths.iter()).cloned().collect(); ms.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); 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(); - Ok(Some(res)) + let res_idl_meths = + idl_meths.iter().chain(idl_serv.iter()).cloned().collect(); + Ok(Some((res, IDLType::ServT(res_idl_meths)))) } }, _ => unreachable!(), @@ -313,7 +344,7 @@ fn merge_actor( } } -fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLProg, Option)> { +fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option)> { let base = if file.is_absolute() { file.parent().unwrap().to_path_buf() } else { @@ -323,13 +354,17 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLProg, Option .unwrap() .to_path_buf() }; - let prog = - std::fs::read_to_string(file).map_err(|_| Error::msg(format!("Cannot open {file:?}")))?; - let prog = if is_pretty { - pretty_parse_idl_prog(file.to_str().unwrap(), &prog)? - } else { - parse_idl_prog(&prog)? + + let prog = { + let prog = std::fs::read_to_string(file) + .map_err(|_| Error::msg(format!("Cannot open {file:?}")))?; + if is_pretty { + pretty_parse_idl_prog(file.to_str().unwrap(), &prog)? + } else { + parse_idl_prog(&prog)? + } }; + let mut visited = BTreeMap::new(); let mut imports = Vec::new(); load_imports(is_pretty, &base, &mut visited, &prog, &mut imports)?; @@ -340,12 +375,16 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLProg, Option None => unreachable!(), }) .collect(); + let mut te = TypeEnv::new(); + let mut idl_env = IDLEnv::new(); let mut env = Env { te: &mut te, + idl_env: &mut idl_env, pre: false, }; - let mut actor: Option = None; + + let mut actor: Option<(Type, IDLType)> = None; for (include_serv, path, name) in imports.iter() { let code = std::fs::read_to_string(path)?; let code = parse_idl_prog(&code)?; @@ -355,18 +394,22 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLProg, Option actor = merge_actor(&env, &actor, &t, name)?; } } + check_decs(&mut env, &prog.decs)?; + let mut res = check_actor(&env, &prog.actor)?; if actor.is_some() { res = merge_actor(&env, &res, &actor, "")?; } - Ok((te, prog, res)) + idl_env.set_actor(res.clone().map(|t| t.1)); + + Ok((te, idl_env, res.map(|t| t.0))) } /// Type check did file including the imports. -pub fn check_file(file: &Path) -> Result<(TypeEnv, IDLProg, Option)> { +pub fn check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { check_file_(file, false) } -pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, IDLProg, Option)> { +pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { check_file_(file, true) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 46ce8ede5..0c232ccb4 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,6 +1,6 @@ use crate::{check_prog, pretty_check_file, pretty_parse_idl_prog, Error, Result}; use candid::{ - types::{syntax::IDLProg, Type, TypeInner}, + types::{syntax::IDLEnv, Type, TypeInner}, TypeEnv, }; use std::path::Path; @@ -11,14 +11,15 @@ pub enum CandidSource<'a> { } impl CandidSource<'_> { - pub fn load(&self) -> Result<(TypeEnv, IDLProg, Option)> { + pub fn load(&self) -> Result<(TypeEnv, IDLEnv, Option)> { Ok(match self { CandidSource::File(path) => pretty_check_file(path)?, CandidSource::Text(str) => { let ast = pretty_parse_idl_prog("", str)?; let mut env = TypeEnv::new(); + let idl_env = IDLEnv::from(&ast); let actor = check_prog(&mut env, &ast)?; - (env, ast, actor) + (env, idl_env, actor) } }) } @@ -88,7 +89,7 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { use crate::{parse_idl_init_args, typing::check_init_args}; use candid::types::TypeInner; let candid = CandidSource::Text(candid); - let (env, _, serv) = candid.load()?; + let (env, mut idl_env, serv) = candid.load()?; let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; let serv = env.trace_type(&serv)?; match serv.as_ref() { @@ -96,7 +97,7 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { TypeInner::Service(_) => { let prog = parse_idl_init_args(init)?; let mut env2 = TypeEnv::new(); - let args = check_init_args(&mut env2, &env, &prog)?; + let args = check_init_args(&mut env2, &env, &mut idl_env, &prog)?; Ok((env2, TypeInner::Class(args, serv).into())) } _ => unreachable!(), @@ -110,7 +111,8 @@ pub fn check_rust_type(candid_args: &str) -> Result<()> { use candid::types::{internal::TypeContainer, subtype::equal, TypeEnv}; let parsed = parse_idl_init_args(candid_args)?; let mut env = TypeEnv::new(); - let args = check_init_args(&mut env, &TypeEnv::new(), &parsed)?; + let mut idl_env = IDLEnv::new(); + let args = check_init_args(&mut env, &TypeEnv::new(), &mut idl_env, &parsed)?; let mut rust_env = TypeContainer::new(); let ty = rust_env.add::(); let ty = env.merge_type(rust_env.env, ty); diff --git a/rust/candid_parser/tests/assets/ok/class.mo b/rust/candid_parser/tests/assets/ok/class.mo index 41ab07b63..b578beba3 100644 --- a/rust/candid_parser/tests/assets/ok/class.mo +++ b/rust/candid_parser/tests/assets/ok/class.mo @@ -2,8 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type List = ?(Int, List); public type Profile = { age : Nat8; name : Text }; + public type List = ?(Int, List); public type Self = (Int, l : List, Profile) -> async actor { get : shared () -> async List; set : shared List -> async List; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.mo b/rust/candid_parser/tests/assets/ok/cyclic.mo index 4ca4a600a..6c7d6f0a4 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.mo +++ b/rust/candid_parser/tests/assets/ok/cyclic.mo @@ -3,8 +3,8 @@ module { public type A = ?B; - public type B = ?C; public type C = A; + public type B = ?C; public type X = Y; public type Y = Z; public type Z = A; diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 76bb1f058..15e37478c 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -4,21 +4,29 @@ module { public type A = B; public type B = ?A; - public type List = ?{ head : Int; tail : List }; - public type a = { #a; #b : b }; + public type node = { head : Nat; tail : list }; + public type list = ?node; + public type tree = { + #branch : { val : Int; left : tree; right : tree }; + #leaf : Int; + }; + public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; + public type t = shared (server : s) -> async (); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type b = (Int, Nat); + public type a = { #a; #b : b }; + public type my_type = Principal; + public type List = ?{ head : Int; tail : List }; + public type f = shared (List, shared Int32 -> async Int64) -> async ( + ?List, + res, + ); public type broker = actor { find : shared (name : Text) -> async actor { current : shared () -> async Nat32; up : shared () -> async (); }; }; - public type f = shared (List, shared Int32 -> async Int64) -> async ( - ?List, - res, - ); - public type list = ?node; - public type my_type = Principal; public type nested = { _0_ : Nat; _1_ : Nat; @@ -28,24 +36,16 @@ module { _41_ : { #_42_ ; #A; #B; #C }; _42_ : Nat; }; + public type res = { #Ok : (Int, Nat); #Err : { error : Text } }; public type nested_res = { #Ok : { #Ok; #Err }; #Err : { #Ok : { content : Text }; #Err : { _0_ : Int } }; }; - public type node = { head : Nat; tail : list }; - public type res = { #Ok : (Int, Nat); #Err : { error : Text } }; - public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared (server : s) -> async (); - public type tree = { - #branch : { val : Int; left : tree; right : tree }; - #leaf : Int; - }; public type Self = actor { bbbbb : shared b -> async (); f : t; - f1 : shared (list, test : Blob, ?Bool) -> (); g : shared list -> async (B, tree, stream); + f1 : shared (list, test : Blob, ?Bool) -> (); g1 : shared query (my_type, List, ?List, nested) -> async ( Int, broker, diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.mo b/rust/candid_parser/tests/assets/ok/fieldnat.mo index f38c0359f..d81c131bb 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.mo +++ b/rust/candid_parser/tests/assets/ok/fieldnat.mo @@ -2,8 +2,8 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type non_tuple = { _1_ : Text; _2_ : Text }; public type tuple = (Text, Text); + public type non_tuple = { _1_ : Text; _2_ : Text }; public type Self = actor { bab : shared (two : Int, Nat) -> async (); bar : shared { _50_ : Int } -> async { #e20; #e30 }; diff --git a/rust/candid_parser/tests/assets/ok/keyword.mo b/rust/candid_parser/tests/assets/ok/keyword.mo index 408ff6f82..f212f52a5 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.mo +++ b/rust/candid_parser/tests/assets/ok/keyword.mo @@ -2,16 +2,16 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { + public type o = ?o; + public type node = { head : Nat; tail : list }; + public type list = ?node; public type if_ = { #branch : { val : Int; left : if_; right : if_ }; #leaf : Int; }; - public type list = ?node; - public type node = { head : Nat; tail : list }; - public type o = ?o; public type return_ = actor { f : t; g : shared list -> async (if_, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type t = shared (server : return_) -> async (); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type Self = actor { Oneway : shared () -> (); f__ : shared o -> async o; diff --git a/rust/candid_parser/tests/assets/ok/management.mo b/rust/candid_parser/tests/assets/ok/management.mo index 0609c7387..1ee97e7f7 100644 --- a/rust/candid_parser/tests/assets/ok/management.mo +++ b/rust/candid_parser/tests/assets/ok/management.mo @@ -2,10 +2,9 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type bitcoin_address = Text; - public type bitcoin_network = { #mainnet; #testnet }; - public type block_hash = Blob; public type canister_id = Principal; + public type user_id = Principal; + public type wasm_module = Blob; public type canister_settings = { freezing_threshold : ?Nat; controllers : ?[Principal]; @@ -18,42 +17,43 @@ module { memory_allocation : Nat; compute_allocation : Nat; }; + public type http_header = { value : Text; name : Text }; + public type http_response = { + status : Nat; + body : Blob; + headers : [http_header]; + }; public type ecdsa_curve = { #secp256k1 }; - public type get_balance_request = { + public type satoshi = Nat64; + public type bitcoin_network = { #mainnet; #testnet }; + public type bitcoin_address = Text; + public type block_hash = Blob; + public type outpoint = { txid : Blob; vout : Nat32 }; + public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; + public type get_utxos_request = { network : bitcoin_network; + filter : ?{ #page : Blob; #min_confirmations : Nat32 }; address : bitcoin_address; - min_confirmations : ?Nat32; }; public type get_current_fee_percentiles_request = { network : bitcoin_network; }; - public type get_utxos_request = { - network : bitcoin_network; - filter : ?{ #page : Blob; #min_confirmations : Nat32 }; - address : bitcoin_address; - }; public type get_utxos_response = { next_page : ?Blob; tip_height : Nat32; tip_block_hash : block_hash; utxos : [utxo]; }; - public type http_header = { value : Text; name : Text }; - public type http_response = { - status : Nat; - body : Blob; - headers : [http_header]; + public type get_balance_request = { + network : bitcoin_network; + address : bitcoin_address; + min_confirmations : ?Nat32; }; - public type millisatoshi_per_byte = Nat64; - public type outpoint = { txid : Blob; vout : Nat32 }; - public type satoshi = Nat64; public type send_transaction_request = { transaction : Blob; network : bitcoin_network; }; - public type user_id = Principal; - public type utxo = { height : Nat32; value : satoshi; outpoint : outpoint }; - public type wasm_module = Blob; + public type millisatoshi_per_byte = Nat64; public type Self = actor { bitcoin_get_balance : shared get_balance_request -> async satoshi; bitcoin_get_current_fee_percentiles : shared get_current_fee_percentiles_request -> async [ diff --git a/rust/candid_parser/tests/assets/ok/recursion.mo b/rust/candid_parser/tests/assets/ok/recursion.mo index 6cc51fe02..675ecc038 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.mo +++ b/rust/candid_parser/tests/assets/ok/recursion.mo @@ -4,14 +4,14 @@ module { public type A = B; public type B = ?A; - public type list = ?node; public type node = { head : Nat; tail : list }; - public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; - public type stream = ?{ head : Nat; next : shared query () -> async stream }; - public type t = shared (server : s) -> async (); + public type list = ?node; public type tree = { #branch : { val : Int; left : tree; right : tree }; #leaf : Int; }; + public type s = actor { f : t; g : shared list -> async (B, tree, stream) }; + public type t = shared (server : s) -> async (); + public type stream = ?{ head : Nat; next : shared query () -> async stream }; public type Self = s } diff --git a/rust/candid_parser/tests/assets/ok/service.mo b/rust/candid_parser/tests/assets/ok/service.mo index 14ee15e7a..c4a66823a 100644 --- a/rust/candid_parser/tests/assets/ok/service.mo +++ b/rust/candid_parser/tests/assets/ok/service.mo @@ -2,9 +2,9 @@ // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. module { - public type Func = shared () -> async Service; public type Service = actor { f : Func }; public type Service2 = Service; + public type Func = shared () -> async Service; public type Self = actor { asArray : shared query () -> async ([Service2], [Func]); asPrincipal : shared () -> async (Service2, Func); diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 96bb80226..716c1e17d 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -40,7 +40,7 @@ fn compiler_test(resource: &str) { let candid_path = base_path.join(filename); match check_file(&candid_path) { - Ok((env, prog, actor)) => { + Ok((env, idl_env, actor)) => { { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); let content = compile(&env, &actor); @@ -52,12 +52,12 @@ fn compiler_test(resource: &str) { { match filename.file_name().unwrap().to_str().unwrap() { "unicode.did" | "escape.did" => { - check_error(|| motoko::compile(&prog), "not a valid Motoko id") + check_error(|| motoko::compile(&idl_env), "not a valid Motoko id") } _ => { let mut output = mint.new_goldenfile(filename.with_extension("mo")).unwrap(); - let content = motoko::compile(&prog); + let content = motoko::compile(&idl_env); writeln!(output, "{content}").unwrap(); } } diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index ff0c2742f..800471ae5 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Result}; use candid_parser::candid::types::{ subtype, - syntax::{IDLProg, IDLType, IDLTypes}, + syntax::{IDLType, IDLTypes}, Type, }; use candid_parser::{ @@ -131,10 +131,11 @@ impl TypeAnnotation { self.tys.is_none() && self.method.is_none() } fn get_types(&self, mode: Mode) -> candid_parser::Result<(TypeEnv, Vec)> { - let (env, _, actor) = if let Some(ref file) = self.defs { - pretty_check_file(file)? + let (env, actor) = if let Some(ref file) = self.defs { + let (env, _, actor) = pretty_check_file(file)?; + (env, actor) } else { - (TypeEnv::new(), IDLProg::default(), None) + (TypeEnv::new(), None) }; match (&self.tys, &self.method) { (None, None) => Err(Error::msg("no type annotations")), @@ -210,10 +211,10 @@ fn main() -> Result<()> { } } Command::Subtype { defs, ty1, ty2 } => { - let (env, _, _) = if let Some(file) = defs { - pretty_check_file(&file)? + let env = if let Some(file) = defs { + pretty_check_file(&file)?.0 } else { - (TypeEnv::new(), IDLProg::default(), None) + TypeEnv::new() }; let ty1 = ast_to_type(&env, &ty1)?; let ty2 = ast_to_type(&env, &ty2)?; From c60bc3f53b0a65e3bc95c515bba39c757443f0cd Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 10:39:15 +0200 Subject: [PATCH 07/34] refactor: use IDL* types in parser (wip) --- rust/candid/src/pretty/candid.rs | 100 ++--- rust/candid/src/types/internal.rs | 20 +- rust/candid/src/types/subtype.rs | 2 +- rust/candid/src/types/syntax.rs | 87 +++- rust/candid_derive/src/func.rs | 3 +- rust/candid_parser/src/bindings/analysis.rs | 101 +++-- rust/candid_parser/src/bindings/javascript.rs | 143 +++---- rust/candid_parser/src/bindings/motoko.rs | 8 +- rust/candid_parser/src/bindings/rust.rs | 381 ++++++++++-------- rust/candid_parser/src/bindings/typescript.rs | 126 +++--- rust/candid_parser/src/configs.rs | 76 ++-- rust/candid_parser/src/random.rs | 116 +++--- rust/candid_parser/src/test.rs | 34 +- rust/candid_parser/src/typing.rs | 220 +++++++--- rust/candid_parser/src/utils.rs | 54 ++- rust/candid_parser/tests/parse_type.rs | 10 +- rust/candid_parser/tests/value.rs | 2 + tools/didc/src/main.rs | 86 ++-- 18 files changed, 932 insertions(+), 637 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 0f3b80ed2..84e10030b 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -1,6 +1,7 @@ use crate::pretty::utils::*; use crate::types::{ - ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner, + syntax::{Binding, FuncType, IDLArgType, IDLEnv, IDLType, PrimType, TypeField}, + FuncMode, Label, }; use pretty::RcDoc; @@ -73,12 +74,8 @@ pub(crate) fn pp_text(id: &str) -> RcDoc { RcDoc::text(ident_string(id)) } -pub fn pp_ty(ty: &Type) -> RcDoc { - pp_ty_inner(ty.as_ref()) -} - -pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { - use TypeInner::*; +fn pp_prim_ty(ty: &PrimType) -> RcDoc { + use PrimType::*; match ty { Null => str("null"), Bool => str("bool"), @@ -97,59 +94,62 @@ pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc { Text => str("text"), Reserved => str("reserved"), Empty => str("empty"), - Var(ref s) => str(s), - Principal => str("principal"), - Opt(ref t) => kwd("opt").append(pp_ty(t)), - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("blob"), - Vec(ref t) => kwd("vec").append(pp_ty(t)), - Record(ref fs) => { - let t = Type(ty.clone().into()); - if t.is_tuple() { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ";"); + } +} + +pub fn pp_ty(ty: &IDLType) -> RcDoc { + use IDLType::*; + match ty { + PrimT(ty) => pp_prim_ty(ty), + VarT(ref s) => str(s), + PrincipalT => str("principal"), + OptT(ref t) => kwd("opt").append(pp_ty(t)), + VecT(ref t) if matches!(t.as_ref(), PrimT(PrimType::Nat8)) => str("blob"), + VecT(ref t) => kwd("vec").append(pp_ty(t)), + RecordT(ref fs) => { + if ty.is_tuple() { + let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ";"); kwd("record").append(enclose_space("{", tuple, "}")) } else { kwd("record").append(pp_fields(fs, false)) } } - Variant(ref fs) => kwd("variant").append(pp_fields(fs, true)), - Func(ref func) => kwd("func").append(pp_function(func)), - Service(ref serv) => kwd("service").append(pp_service(serv)), - Class(ref args, ref t) => { + VariantT(ref fs) => kwd("variant").append(pp_fields(fs, true)), + FuncT(ref func) => kwd("func").append(pp_function(func)), + ServT(ref serv) => kwd("service").append(pp_service(serv)), + ClassT(ref args, ref t) => { let doc = pp_args(args).append(" ->").append(RcDoc::space()); match t.as_ref() { - Service(ref serv) => doc.append(pp_service(serv)), - Var(ref s) => doc.append(s), + IDLType::ServT(ref serv) => doc.append(pp_service(serv)), + IDLType::VarT(ref s) => doc.append(s), _ => unreachable!(), } } - Knot(ref id) => RcDoc::text(format!("{id}")), - Unknown => str("unknown"), - Future => str("future"), } } -pub fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +pub fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(id) => pp_text(id), Label::Id(_) | Label::Unnamed(_) => RcDoc::as_string(id), } } -pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc { - let ty_doc = if is_variant && *field.ty == TypeInner::Null { +pub(crate) fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc { + let ty_doc = if is_variant && field.typ == IDLType::PrimT(PrimType::Null) { RcDoc::nil() } else { - kwd(" :").append(pp_ty(&field.ty)) + kwd(" :").append(pp_ty(&field.typ)) }; - pp_label(&field.id).append(ty_doc) + pp_label(&field.label).append(ty_doc) } -fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc { +fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc { let fields = concat(fs.iter().map(|f| pp_field(f, is_variant)), ";"); enclose_space("{", fields, "}") } -pub fn pp_function(func: &Function) -> RcDoc { +pub fn pp_function(func: &FuncType) -> RcDoc { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -159,7 +159,7 @@ pub fn pp_function(func: &Function) -> RcDoc { .nest(INDENT_SPACE) } -pub fn pp_args(args: &[ArgType]) -> RcDoc { +pub fn pp_args(args: &[IDLArgType]) -> RcDoc { let args = args.iter().map(|arg| { if let Some(name) = &arg.name { pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ)) @@ -171,7 +171,7 @@ pub fn pp_args(args: &[ArgType]) -> RcDoc { enclose("(", doc, ")") } -pub fn pp_rets(rets: &[Type]) -> RcDoc { +pub fn pp_rets(rets: &[IDLType]) -> RcDoc { let doc = concat(rets.iter().map(pp_ty), ","); enclose("(", doc, ")") } @@ -187,12 +187,12 @@ pub fn pp_modes(modes: &[FuncMode]) -> RcDoc { RcDoc::concat(modes.iter().map(|m| RcDoc::space().append(pp_mode(m)))) } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[Binding]) -> RcDoc { let doc = concat( - serv.iter().map(|(id, func)| { - let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => pp_function(f), - TypeInner::Var(_) => pp_ty(func), + serv.iter().map(|Binding { id, typ }| { + let func_doc = match typ { + IDLType::FuncT(ref f) => pp_function(f), + IDLType::VarT(_) => pp_ty(typ), _ => unreachable!(), }; pp_text(id).append(kwd(" :")).append(func_doc) @@ -202,29 +202,29 @@ fn pp_service(serv: &[(String, Type)]) -> RcDoc { enclose_space("{", doc, "}") } -fn pp_defs(env: &TypeEnv) -> RcDoc { - lines(env.0.iter().map(|(id, ty)| { +fn pp_defs(env: &IDLEnv) -> RcDoc { + lines(env.types_bindings.iter().map(|Binding { id, typ }| { kwd("type") .append(ident(id)) .append(kwd("=")) - .append(pp_ty(ty)) + .append(pp_ty(typ)) .append(";") })) } -fn pp_actor(ty: &Type) -> RcDoc { - match ty.as_ref() { - TypeInner::Service(ref serv) => pp_service(serv), - TypeInner::Var(_) | TypeInner::Class(_, _) => pp_ty(ty), +fn pp_actor(ty: &IDLType) -> RcDoc { + match ty { + IDLType::ServT(ref serv) => pp_service(serv), + IDLType::VarT(_) | IDLType::ClassT(_, _) => pp_ty(ty), _ => unreachable!(), } } -pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [ArgType]) -> RcDoc<'a> { +pub fn pp_init_args<'a>(env: &'a IDLEnv, args: &'a [IDLArgType]) -> RcDoc<'a> { pp_defs(env).append(pp_args(args)) } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { - match actor { +pub fn compile(env: &IDLEnv) -> String { + match &env.actor { None => pp_defs(env).pretty(LINE_WIDTH).to_string(), Some(actor) => { let defs = pp_defs(env); diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 4a70cdba0..9c4d47717 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -325,13 +325,21 @@ impl Type { #[cfg(feature = "printer")] impl fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", crate::pretty::candid::pp_ty(self).pretty(80)) + write!( + f, + "{}", + "TODO" // crate::pretty::candid::pp_ty(self).pretty(80), + ) } } #[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, + "{}", + "TODO" // crate::pretty::candid::pp_ty_inner(self).pretty(80), + ) } } #[cfg(not(feature = "printer"))] @@ -483,7 +491,7 @@ impl fmt::Display for Field { write!( f, "{}", - crate::pretty::candid::pp_field(self, false).pretty(80) + "TODO" // crate::pretty::candid::pp_field(self, false).pretty(80) ) } } @@ -557,7 +565,11 @@ pub struct ArgType { #[cfg(feature = "printer")] impl fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", crate::pretty::candid::pp_function(self).pretty(80)) + write!( + f, + "{}", + "TODO" // crate::pretty::candid::pp_function(self).pretty(80), + ) } } #[cfg(not(feature = "printer"))] diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 1e002317d..fbd4c8ec8 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -318,5 +318,5 @@ fn pp_args(args: &[crate::types::ArgType]) -> String { #[cfg(feature = "printer")] fn pp_args(args: &[crate::types::ArgType]) -> String { use crate::pretty::candid::pp_args; - pp_args(args).pretty(80).to_string() + "TODO".to_string() // pp_args(args).pretty(80).to_string() } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 208479ab3..a029830f1 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::{collections::BTreeSet, fmt}; use crate::types::{FuncMode, Label}; @@ -16,6 +16,28 @@ pub enum IDLType { PrincipalT, } +impl IDLType { + pub fn is_tuple(&self) -> bool { + match self { + IDLType::RecordT(fields) => { + for (i, field) in fields.iter().enumerate() { + if field.label.get_id() != (i as u32) { + return false; + } + } + true + } + _ => false, + } + } +} + +impl fmt::Display for IDLType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", crate::pretty::candid::pp_ty(self).pretty(80)) + } +} + #[derive(Debug, Clone)] pub struct IDLTypes { pub args: Vec, @@ -154,36 +176,89 @@ impl From<&IDLProg> for IDLEnv { } } +impl From> for IDLEnv { + fn from(bindings: Vec<&Binding>) -> Self { + let mut env = Self::default(); + for binding in bindings { + env.insert_binding(binding.clone()); + } + env + } +} + impl IDLEnv { pub fn new() -> Self { Self::default() } pub fn insert_binding(&mut self, binding: Binding) { - let is_duplicate = self.types_bindings_ids.insert(binding.id.clone()); - if !is_duplicate { + let is_new = self.types_bindings_ids.insert(binding.id.clone()); + if is_new { self.types_bindings.push(binding); } } + pub fn bindings_ids(&self) -> Vec<&str> { + self.types_bindings_ids + .iter() + .map(|id| id.as_str()) + .collect() + } + pub fn set_actor(&mut self, actor: Option) { self.actor = actor; } - pub fn find_type(&self, id: &str) -> Result<&IDLType, String> { + pub fn find_binding(&self, id: &str) -> Result<&Binding, String> { self.types_bindings .iter() .find(|b| b.id == id) - .map(|b| &b.typ) .ok_or(format!("Unbound type identifier: {id}")) } + pub fn find_type(&self, id: &str) -> Result<&IDLType, String> { + self.find_binding(id).map(|b| &b.typ) + } + + pub fn rec_find_type(&self, name: &str) -> Result<&IDLType, String> { + let t = self.find_type(name)?; + match t { + IDLType::VarT(id) => self.rec_find_type(id), + _ => Ok(t), + } + } + + pub fn trace_type(&self, t: &IDLType) -> Result { + match t { + IDLType::VarT(id) => self.trace_type(self.find_type(id)?), + IDLType::ClassT(_, t) => self.trace_type(t), + _ => Ok(t.clone()), + } + } + pub fn as_service<'a>(&'a self, t: &'a IDLType) -> Result<&'a Vec, String> { match t { IDLType::ServT(methods) => Ok(methods), IDLType::VarT(id) => self.as_service(self.find_type(id)?), IDLType::ClassT(_, t) => self.as_service(t), - _ => Err(format!("not a service type: {:?}", t)), + _ => Err(format!("not a service type: {t}")), + } + } + + pub fn as_func<'a>(&'a self, t: &'a IDLType) -> Result<&'a FuncType, String> { + match t { + IDLType::FuncT(f) => Ok(f), + IDLType::VarT(id) => self.as_func(self.find_type(id)?), + _ => Err(format!("not a function type: {:?}", t)), + } + } + + pub fn get_method<'a>(&'a self, t: &'a IDLType, id: &'a str) -> Result<&'a FuncType, String> { + for binding in self.as_service(t)? { + if binding.id == id { + return self.as_func(&binding.typ); + } } + Err(format!("cannot find method {id}")) } } diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 7c886563e..f3df9104c 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -140,7 +140,8 @@ pub(crate) fn export_service(path: Option) -> TokenStream { fn __export_service() -> String { #service #actor - let result = #candid::pretty::candid::compile(&env.env, &actor); + // let result = #candid::pretty::candid::compile(&env.env, &actor); + let result = "TODO"; format!("{}", result) } }; diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index bf80a0524..4962815fe 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -1,5 +1,8 @@ use crate::{Error, Result}; -use candid::types::{Type, TypeEnv, TypeInner}; +use candid::types::{ + syntax::{IDLEnv, IDLType}, + Type, TypeEnv, TypeInner, +}; use std::collections::{BTreeMap, BTreeSet}; /// Select a subset of methods from an actor. @@ -37,36 +40,38 @@ pub fn project_methods( pub fn chase_type<'a>( seen: &mut BTreeSet<&'a str>, res: &mut Vec<&'a str>, - env: &'a TypeEnv, - t: &'a Type, + env: &'a IDLEnv, + t: &'a IDLType, ) -> Result<()> { - use TypeInner::*; - match t.as_ref() { - Var(id) => { + use IDLType::*; + match t { + VarT(id) => { if seen.insert(id) { - let t = env.find_type(id)?; + let t = env + .find_type(id) + .map_err(|e| Error::Custom(anyhow::anyhow!(e)))?; chase_type(seen, res, env, t)?; res.push(id); } } - Opt(ty) | Vec(ty) => chase_type(seen, res, env, ty)?, - Record(fs) | Variant(fs) => { + OptT(ty) | VecT(ty) => chase_type(seen, res, env, ty)?, + RecordT(fs) | VariantT(fs) => { for f in fs.iter() { - chase_type(seen, res, env, &f.ty)?; + chase_type(seen, res, env, &f.typ)?; } } - Func(f) => { + FuncT(f) => { let args = f.args.iter().map(|arg| &arg.typ); for ty in args.clone().chain(f.rets.iter()) { chase_type(seen, res, env, ty)?; } } - Service(ms) => { - for (_, ty) in ms.iter() { - chase_type(seen, res, env, ty)?; + ServT(bindings) => { + for binding in bindings.iter() { + chase_type(seen, res, env, &binding.typ)?; } } - Class(args, t) => { + ClassT(args, t) => { for arg in args.iter() { chase_type(seen, res, env, &arg.typ)?; } @@ -79,20 +84,27 @@ pub fn chase_type<'a>( /// Gather type definitions mentioned in actor, return the non-recursive type names in topological order. /// Recursive types can appear in any order. -pub fn chase_actor<'a>(env: &'a TypeEnv, actor: &'a Type) -> Result> { +pub fn chase_actor<'a>(env: &'a IDLEnv) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); + let actor = env + .actor + .as_ref() + .ok_or_else(|| Error::Custom(anyhow::anyhow!("no actor")))?; chase_type(&mut seen, &mut res, env, actor)?; Ok(res) } /// Given an actor, return a map from variable names to the (methods, arg) that use them. -pub fn chase_def_use<'a>( - env: &'a TypeEnv, - actor: &'a Type, -) -> Result>> { +pub fn chase_def_use<'a>(env: &'a IDLEnv) -> Result>> { let mut res = BTreeMap::new(); - let actor = env.trace_type(actor)?; - if let TypeInner::Class(args, _) = actor.as_ref() { + let actor = env + .actor + .as_ref() + .ok_or_else(|| Error::Custom(anyhow::anyhow!("no actor")))?; + let actor = env + .trace_type(&actor) + .map_err(|e| Error::Custom(anyhow::anyhow!(e)))?; + if let IDLType::ClassT(args, _) = &actor { for (i, arg) in args.iter().enumerate() { let mut used = Vec::new(); chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; @@ -103,15 +115,20 @@ pub fn chase_def_use<'a>( } } } - for (id, ty) in env.as_service(&actor)? { - let func = env.as_func(ty)?; + for binding in env + .as_service(&actor) + .map_err(|e| Error::Custom(anyhow::anyhow!(e)))? + { + let func = env + .as_func(&binding.typ) + .map_err(|e| Error::Custom(anyhow::anyhow!(e)))?; for (i, arg) in func.args.iter().enumerate() { let mut used = Vec::new(); chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) - .push(format!("{}.arg{}", id, i)); + .push(format!("{}.arg{}", binding.id, i)); } } for (i, arg) in func.rets.iter().enumerate() { @@ -120,14 +137,14 @@ pub fn chase_def_use<'a>( for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) - .push(format!("{}.ret{}", id, i)); + .push(format!("{}.ret{}", binding.id, i)); } } } Ok(res) } -pub fn chase_types<'a>(env: &'a TypeEnv, tys: &'a [Type]) -> Result> { +pub fn chase_types<'a>(env: &'a IDLEnv, tys: &'a [IDLType]) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); for t in tys.iter() { @@ -137,40 +154,40 @@ pub fn chase_types<'a>(env: &'a TypeEnv, tys: &'a [Type]) -> Result } /// Given a `def_list` produced by the `chase_actor` function, infer which types are recursive -pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result> { +pub fn infer_rec<'a>(_env: &'a IDLEnv, def_list: &'a [&'a str]) -> Result> { let mut seen = BTreeSet::new(); let mut res = BTreeSet::new(); fn go<'a>( seen: &mut BTreeSet<&'a str>, res: &mut BTreeSet<&'a str>, - _env: &'a TypeEnv, - t: &'a Type, + _env: &'a IDLEnv, + t: &'a IDLType, ) -> Result<()> { - use TypeInner::*; - match t.as_ref() { - Var(id) => { + use IDLType::*; + match t { + VarT(id) => { if seen.insert(id) { res.insert(id); } } - Opt(ty) | Vec(ty) => go(seen, res, _env, ty)?, - Record(fs) | Variant(fs) => { + OptT(ty) | VecT(ty) => go(seen, res, _env, ty)?, + RecordT(fs) | VariantT(fs) => { for f in fs.iter() { - go(seen, res, _env, &f.ty)?; + go(seen, res, _env, &f.typ)?; } } - Func(f) => { + FuncT(f) => { let args = f.args.iter().map(|arg| &arg.typ); for ty in args.clone().chain(f.rets.iter()) { go(seen, res, _env, ty)?; } } - Service(ms) => { - for (_, ty) in ms.iter() { - go(seen, res, _env, ty)?; + ServT(ms) => { + for binding in ms.iter() { + go(seen, res, _env, &binding.typ)?; } } - Class(args, t) => { + ClassT(args, t) => { for arg in args.iter() { go(seen, res, _env, &arg.typ)?; } @@ -181,7 +198,7 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result bool { - match t.as_ref() { - TypeInner::Record(ref fs) => { +pub(super) fn is_tuple(t: &IDLType) -> bool { + match t { + IDLType::RecordT(ref fs) => { if fs.is_empty() { return false; } - for (i, field) in fs.iter().enumerate() { - if field.id.get_id() != (i as u32) { - return false; - } - } - true + t.is_tuple() } _ => false, } @@ -96,9 +94,9 @@ pub(crate) fn ident(id: &str) -> RcDoc { } } -fn pp_ty(ty: &Type) -> RcDoc { - use TypeInner::*; - match ty.as_ref() { +fn pp_prim_ty(ty: &PrimType) -> RcDoc { + use PrimType::*; + match ty { Null => str("IDL.Null"), Bool => str("IDL.Bool"), Nat => str("IDL.Nat"), @@ -116,28 +114,34 @@ fn pp_ty(ty: &Type) -> RcDoc { Text => str("IDL.Text"), Reserved => str("IDL.Reserved"), Empty => str("IDL.Empty"), - Var(ref s) => ident(s), - Principal => str("IDL.Principal"), - Opt(ref t) => str("IDL.Opt").append(enclose("(", pp_ty(t), ")")), - Vec(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), - Record(ref fs) => { + } +} + +fn pp_ty(ty: &IDLType) -> RcDoc { + use IDLType::*; + match ty { + PrimT(ty) => pp_prim_ty(ty), + VarT(ref s) => ident(s), + PrincipalT => str("IDL.Principal"), + OptT(ref t) => str("IDL.Opt").append(enclose("(", pp_ty(t), ")")), + VecT(ref t) => str("IDL.Vec").append(enclose("(", pp_ty(t), ")")), + RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ","); str("IDL.Tuple").append(enclose("(", tuple, ")")) } else { str("IDL.Record").append(pp_fields(fs)) } } - Variant(ref fs) => str("IDL.Variant").append(pp_fields(fs)), - Func(ref func) => str("IDL.Func").append(pp_function(func)), - Service(ref serv) => str("IDL.Service").append(pp_service(serv)), - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), + VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), + FuncT(ref func) => str("IDL.Func").append(pp_function(func)), + ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), + ClassT(_, _) => unreachable!(), } } -fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") .append(RcDoc::as_string(n)) @@ -146,18 +150,18 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field(field: &Field) -> RcDoc { - pp_label(&field.id) +fn pp_field(field: &TypeField) -> RcDoc { + pp_label(&field.label) .append(kwd(":")) - .append(pp_ty(&field.ty)) + .append(pp_ty(&field.typ)) } -fn pp_fields(fs: &[Field]) -> RcDoc { +fn pp_fields(fs: &[TypeField]) -> RcDoc { let fields = concat(fs.iter().map(pp_field), ","); enclose_space("({", fields, "})") } -fn pp_function(func: &Function) -> RcDoc { +fn pp_function(func: &FuncType) -> RcDoc { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); let modes = pp_modes(&func.modes); @@ -166,12 +170,12 @@ fn pp_function(func: &Function) -> RcDoc { enclose("(", doc, ")").nest(INDENT_SPACE) } -fn pp_args(args: &[ArgType]) -> RcDoc { +fn pp_args(args: &[IDLArgType]) -> RcDoc { let doc = concat(args.iter().map(|arg| pp_ty(&arg.typ)), ","); enclose("[", doc, "]") } -fn pp_rets(args: &[Type]) -> RcDoc { +fn pp_rets<'a>(args: &'a [IDLType]) -> RcDoc<'a> { let doc = concat(args.iter().map(pp_ty), ","); enclose("[", doc, "]") } @@ -186,20 +190,16 @@ fn pp_modes(modes: &[candid::types::FuncMode]) -> RcDoc { enclose("[", doc, "]") } -fn pp_service(serv: &[(String, Type)]) -> RcDoc { +fn pp_service(serv: &[Binding]) -> RcDoc { let doc = concat( serv.iter() - .map(|(id, func)| quote_ident(id).append(kwd(":")).append(pp_ty(func))), + .map(|Binding { id, typ }| quote_ident(id).append(kwd(":")).append(pp_ty(typ))), ",", ); enclose_space("({", doc, "})") } -fn pp_defs<'a>( - env: &'a TypeEnv, - def_list: &'a [&'a str], - recs: &'a BTreeSet<&'a str>, -) -> RcDoc<'a> { +fn pp_defs<'a>(env: &'a IDLEnv, def_list: &'a [&'a str], recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { let recs_doc = lines( recs.iter() .map(|id| kwd("const").append(ident(id)).append(" = IDL.Rec();")), @@ -221,39 +221,39 @@ fn pp_defs<'a>( recs_doc.append(defs) } -fn pp_actor<'a>(ty: &'a Type, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { - match ty.as_ref() { - TypeInner::Service(_) => pp_ty(ty), - TypeInner::Var(id) => { +fn pp_actor<'a>(ty: &'a IDLType, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { + match ty { + IDLType::ServT(_) => pp_ty(ty), + IDLType::VarT(id) => { if recs.contains(&*id.clone()) { str(id).append(".getType()") } else { str(id) } } - TypeInner::Class(_, t) => pp_actor(t, recs), + IDLType::ClassT(_, t) => pp_actor(t, recs), _ => unreachable!(), } } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { - match actor { +pub fn compile(env: &IDLEnv) -> String { + match &env.actor { None => { - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); + let def_list: Vec<_> = env.bindings_ids(); let recs = infer_rec(env, &def_list).unwrap(); let doc = pp_defs(env, &def_list, &recs); doc.pretty(LINE_WIDTH).to_string() } Some(actor) => { - let def_list = chase_actor(env, actor).unwrap(); + let def_list = chase_actor(env).unwrap(); let recs = infer_rec(env, &def_list).unwrap(); let defs = pp_defs(env, &def_list, &recs); - let types = if let TypeInner::Class(ref args, _) = actor.as_ref() { + let init = if let IDLType::ClassT(ref args, _) = actor { args.iter().map(|arg| arg.typ.clone()).collect::>() } else { Vec::new() }; - let init = types.as_slice(); + let init = init.as_slice(); let actor = kwd("return").append(pp_actor(actor, &recs)).append(";"); let body = defs.append(actor); let doc = str("export const idlFactory = ({ IDL }) => ") @@ -368,10 +368,10 @@ pub mod value { pub mod test { use super::value; - use crate::test::{HostAssert, HostTest, Test}; + use crate::test::{to_idl_types, HostAssert, HostTest, Test}; use candid::pretty::utils::*; - use candid::types::syntax::IDLProg; - use candid::TypeEnv; + use candid::types::syntax::{IDLEnv, IDLType}; + use candid::types::{syntax::IDLProg, TypeEnv}; use pretty::RcDoc; fn pp_hex(bytes: &[u8]) -> RcDoc { @@ -379,7 +379,7 @@ pub mod test { .append(RcDoc::as_string(hex::encode(bytes))) .append("', 'hex')") } - fn pp_encode<'a>(args: &'a candid::IDLArgs, tys: &'a [candid::types::Type]) -> RcDoc<'a> { + fn pp_encode<'a>(args: &'a candid::IDLArgs, tys: &'a [IDLType]) -> RcDoc<'a> { let vals = value::pp_args(args); let tys = super::pp_rets(tys); let items = [tys, vals]; @@ -387,7 +387,7 @@ pub mod test { str("IDL.encode").append(enclose("(", params, ")")) } - fn pp_decode<'a>(bytes: &'a [u8], tys: &'a [candid::types::Type]) -> RcDoc<'a> { + fn pp_decode<'a>(bytes: &'a [u8], tys: &'a [IDLType]) -> RcDoc<'a> { let hex = pp_hex(bytes); let tys = super::pp_rets(tys); let items = [tys, hex]; @@ -404,19 +404,17 @@ import { Principal } from './principal'; "#; let mut res = header.to_string(); let mut env = TypeEnv::new(); - crate::check_prog( - &mut env, - &IDLProg { - decs: test.defs, - actor: None, - }, - ) - .unwrap(); - res += &super::compile(&env, &None); + let idl_prog = IDLProg { + decs: test.defs, + actor: None, + }; + let idl_env = IDLEnv::from(&idl_prog); + crate::check_prog(&mut env, &idl_prog).unwrap(); + res += &super::compile(&idl_env); for (i, assert) in test.asserts.iter().enumerate() { let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push(crate::typing::ast_to_type(&env, ty).unwrap()); + types.push((ty.clone(), crate::typing::ast_to_type(&env, ty).unwrap())); } let host = HostTest::from_assert(assert, &env, &types); let mut expects = Vec::new(); @@ -424,11 +422,15 @@ import { Principal } from './principal'; use HostAssert::*; let test_func = match cmd { Encode(args, tys, _, _) | NotEncode(args, tys) => { - let items = [super::pp_rets(tys), pp_encode(args, tys)]; + let idl_tys = to_idl_types(&tys); + let items = [super::pp_rets(&[]), pp_encode(args, &[])]; let params = concat(items.iter().cloned(), ","); str("IDL.decode").append(enclose("(", params, ")")) } - Decode(bytes, tys, _, _) | NotDecode(bytes, tys) => pp_decode(bytes, tys), + Decode(bytes, tys, _, _) | NotDecode(bytes, tys) => { + let idl_tys = to_idl_types(&tys); + pp_decode(bytes, &[]) + } }; let (test_func, predicate) = match cmd { Encode(_, _, true, _) | Decode(_, _, true, _) => (test_func, str(".toEqual")), @@ -440,7 +442,10 @@ import { Principal } from './principal'; } }; let expected = match cmd { - Encode(_, tys, _, bytes) => pp_decode(bytes, tys), + Encode(_, tys, _, bytes) => { + let idl_tys = to_idl_types(&tys); + pp_decode(bytes, &[]) + } Decode(_, _, _, vals) => value::pp_args(vals), NotEncode(_, _) | NotDecode(_, _) => RcDoc::nil(), }; diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 28a6d74a4..4219f2579 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -9,19 +9,13 @@ use candid::types::{ }; use pretty::RcDoc; -// The definition of tuple is language specific. fn is_tuple(t: &IDLType) -> bool { match t { IDLType::RecordT(ref fs) => { if fs.len() <= 1 { return false; } - for (i, field) in fs.iter().enumerate() { - if field.label.get_id() != (i as u32) { - return false; - } - } - true + t.is_tuple() } _ => false, } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 4fa80e7e1..344f7e77b 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -3,8 +3,11 @@ use crate::{ configs::{ConfigState, ConfigTree, Configs, Context, StateElem}, Deserialize, }; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; -use candid::{pretty::utils::*, types::ArgType}; +use candid::pretty::utils::*; +use candid::types::{ + syntax::{Binding, FuncType, IDLArgType, IDLEnv, IDLType, PrimType, TypeField}, + Label, +}; use convert_case::{Case, Casing}; use pretty::RcDoc; use serde::Serialize; @@ -82,26 +85,35 @@ struct State<'a> { type RecPoints<'a> = BTreeSet<&'a str>; // The definition of tuple is language specific. -pub(crate) fn is_tuple(fs: &[Field]) -> bool { - if fs.is_empty() { - return false; +pub(crate) fn is_tuple(t: &IDLType) -> bool { + match t { + IDLType::RecordT(ref fs) => { + if fs.is_empty() { + return false; + } + t.is_tuple() + } + _ => false, } - !fs.iter() - .enumerate() - .any(|(i, field)| field.id.get_id() != (i as u32)) } -fn as_result(fs: &[Field]) -> Option<(&Type, &Type, bool)> { +fn as_result(fs: &[TypeField]) -> Option<(&IDLType, &IDLType, bool)> { match fs { - [Field { id: ok, ty: t_ok }, Field { id: err, ty: t_err }] - if **ok == Label::Named("Ok".to_string()) - && **err == Label::Named("Err".to_string()) => - { + [TypeField { + label: ok, + typ: t_ok, + }, TypeField { + label: err, + typ: t_err, + }] if *ok == Label::Named("Ok".to_string()) && *err == Label::Named("Err".to_string()) => { Some((t_ok, t_err, false)) } - [Field { id: ok, ty: t_ok }, Field { id: err, ty: t_err }] - if **ok == Label::Named("ok".to_string()) - && **err == Label::Named("err".to_string()) => - { + [TypeField { + label: ok, + typ: t_ok, + }, TypeField { + label: err, + typ: t_err, + }] if *ok == Label::Named("ok".to_string()) && *err == Label::Named("err".to_string()) => { Some((t_ok, t_err, true)) } _ => None, @@ -154,23 +166,22 @@ fn pp_vis<'a>(vis: &Option) -> RcDoc<'a> { } impl<'a> State<'a> { - fn generate_test(&mut self, src: &Type, use_type: &str) { + fn generate_test(&mut self, src: &IDLType, use_type: &str) { if self.tests.contains_key(use_type) { return; } - let def_list = chase_actor(self.state.env, src).unwrap(); - let env = TypeEnv( + let def_list = chase_actor(self.state.env).unwrap(); + let env = IDLEnv::from( self.state .env - .0 + .types_bindings .iter() - .filter(|(k, _)| def_list.contains(&k.as_str())) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), + .filter(|Binding { id, typ: _ }| def_list.contains(&id.as_str())) + .collect::>(), ); let src = candid::pretty::candid::pp_init_args( &env, - &[ArgType { + &[IDLArgType { name: None, typ: src.clone(), }], @@ -189,8 +200,8 @@ fn test_{test_name}() {{ ); self.tests.insert(use_type.to_string(), body); } - fn pp_ty<'b>(&mut self, ty: &'b Type, is_ref: bool) -> RcDoc<'b> { - use TypeInner::*; + fn pp_ty<'b>(&mut self, ty: &'b IDLType, is_ref: bool) -> RcDoc<'b> { + use IDLType::*; let elem = StateElem::Type(ty); let old = self.state.push_state(&elem); let res = if let Some(t) = &self.state.config.use_type { @@ -202,25 +213,25 @@ fn test_{test_name}() {{ self.state.update_stats("use_type"); res } else { - match ty.as_ref() { - Null => str("()"), - Bool => str("bool"), - Nat => str("candid::Nat"), - Int => str("candid::Int"), - Nat8 => str("u8"), - Nat16 => str("u16"), - Nat32 => str("u32"), - Nat64 => str("u64"), - Int8 => str("i8"), - Int16 => str("i16"), - Int32 => str("i32"), - Int64 => str("i64"), - Float32 => str("f32"), - Float64 => str("f64"), - Text => str("String"), - Reserved => str("candid::Reserved"), - Empty => str("candid::Empty"), - Var(ref id) => { + match ty { + PrimT(PrimType::Null) => str("()"), + PrimT(PrimType::Bool) => str("bool"), + PrimT(PrimType::Nat) => str("candid::Nat"), + PrimT(PrimType::Int) => str("candid::Int"), + PrimT(PrimType::Nat8) => str("u8"), + PrimT(PrimType::Nat16) => str("u16"), + PrimT(PrimType::Nat32) => str("u32"), + PrimT(PrimType::Nat64) => str("u64"), + PrimT(PrimType::Int8) => str("i8"), + PrimT(PrimType::Int16) => str("i16"), + PrimT(PrimType::Int32) => str("i32"), + PrimT(PrimType::Int64) => str("i64"), + PrimT(PrimType::Float32) => str("f32"), + PrimT(PrimType::Float64) => str("f64"), + PrimT(PrimType::Text) => str("String"), + PrimT(PrimType::Reserved) => str("candid::Reserved"), + PrimT(PrimType::Empty) => str("candid::Empty"), + VarT(ref id) => { let name = if let Some(name) = &self.state.config.name { let res = RcDoc::text(name.clone()); self.state.update_stats("name"); @@ -234,13 +245,15 @@ fn test_{test_name}() {{ name } } - Principal => str("Principal"), - Opt(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), + PrincipalT => str("Principal"), + OptT(ref t) => str("Option").append(enclose("<", self.pp_ty(t, is_ref), ">")), // It's a bit tricky to use `deserialize_with = "serde_bytes"`. It's not working for `type t = blob` - Vec(ref t) if matches!(t.as_ref(), Nat8) => str("serde_bytes::ByteBuf"), - Vec(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), - Record(ref fs) => self.pp_record_fields(fs, false, is_ref), - Variant(ref fs) => { + VecT(ref t) if matches!(t.as_ref(), PrimT(PrimType::Nat8)) => { + str("serde_bytes::ByteBuf") + } + VecT(ref t) => str("Vec").append(enclose("<", self.pp_ty(t, is_ref), ">")), + RecordT(ref fs) => self.pp_record_fields(fs, false, is_ref), + VariantT(ref fs) => { // only possible for result variant let (ok, err, is_motoko) = as_result(fs).unwrap(); // This is a hacky way to redirect Result type @@ -268,16 +281,15 @@ fn test_{test_name}() {{ let body = ok.append(", ").append(err); RcDoc::text(result).append(enclose("<", body, ">")) } - Func(_) => unreachable!(), // not possible after rewriting - Service(_) => unreachable!(), // not possible after rewriting - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), + FuncT(_) => unreachable!(), // not possible after rewriting + ServT(_) => unreachable!(), // not possible after rewriting + ClassT(_, _) => unreachable!(), } }; self.state.pop_state(old, elem); res } - fn pp_label<'b>(&mut self, id: &'b SharedLabel, is_variant: bool, need_vis: bool) -> RcDoc<'b> { + fn pp_label<'b>(&mut self, id: &'b Label, is_variant: bool, need_vis: bool) -> RcDoc<'b> { let vis = if need_vis { self.state.update_stats("visibility"); pp_vis(&self.state.config.visibility) @@ -292,7 +304,7 @@ fn test_{test_name}() {{ .map(|s| RcDoc::text(s).append(RcDoc::line())) .unwrap_or(RcDoc::nil()); self.state.update_stats("attributes"); - match &**id { + match id { Label::Named(id) => { let (doc, is_rename) = if let Some(name) = &self.state.config.name { let res = (RcDoc::text(name.clone()), true); @@ -324,7 +336,7 @@ fn test_{test_name}() {{ } } } - fn pp_tuple<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { + fn pp_tuple<'b>(&mut self, fs: &'b [TypeField], need_vis: bool, is_ref: bool) -> RcDoc<'b> { let tuple = fs.iter().enumerate().map(|(i, f)| { let lab = i.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); @@ -334,30 +346,40 @@ fn test_{test_name}() {{ } else { RcDoc::nil() }; - let res = vis.append(self.pp_ty(&f.ty, is_ref)).append(","); + let res = vis.append(self.pp_ty(&f.typ, is_ref)).append(","); self.state.pop_state(old, StateElem::Label(&lab)); res }); enclose("(", RcDoc::concat(tuple), ")") } - fn pp_record_field<'b>(&mut self, field: &'b Field, need_vis: bool, is_ref: bool) -> RcDoc<'b> { - let lab = field.id.to_string(); + fn pp_record_field<'b>( + &mut self, + field: &'b TypeField, + need_vis: bool, + is_ref: bool, + ) -> RcDoc<'b> { + let lab = field.label.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); let res = self - .pp_label(&field.id, false, need_vis) + .pp_label(&field.label, false, need_vis) .append(kwd(":")) - .append(self.pp_ty(&field.ty, is_ref)); + .append(self.pp_ty(&field.typ, is_ref)); self.state.pop_state(old, StateElem::Label(&lab)); res } - fn pp_record_fields<'b>(&mut self, fs: &'b [Field], need_vis: bool, is_ref: bool) -> RcDoc<'b> { + fn pp_record_fields<'b>( + &mut self, + fs: &'b [TypeField], + need_vis: bool, + is_ref: bool, + ) -> RcDoc<'b> { let old = if self.state.path.last() == Some(&"record".to_string()) { // don't push record again when coming from pp_ty None } else { Some(self.state.push_state(&StateElem::TypeStr("record"))) }; - let res = if is_tuple(fs) { + let res = if is_tuple(&IDLType::RecordT(fs.to_vec())) { self.pp_tuple(fs, need_vis, is_ref) } else { let fields: Vec<_> = fs @@ -372,24 +394,24 @@ fn test_{test_name}() {{ } res } - fn pp_variant_field<'b>(&mut self, field: &'b Field) -> RcDoc<'b> { - let lab = field.id.to_string(); + fn pp_variant_field<'b>(&mut self, field: &'b TypeField) -> RcDoc<'b> { + let lab = field.label.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); - let res = match field.ty.as_ref() { - TypeInner::Null => self.pp_label(&field.id, true, false), - TypeInner::Record(fs) => self - .pp_label(&field.id, true, false) + let res = match &field.typ { + IDLType::PrimT(PrimType::Null) => self.pp_label(&field.label, true, false), + IDLType::RecordT(fs) => self + .pp_label(&field.label, true, false) .append(self.pp_record_fields(fs, false, false)), - _ => self.pp_label(&field.id, true, false).append(enclose( + _ => self.pp_label(&field.label, true, false).append(enclose( "(", - self.pp_ty(&field.ty, false), + self.pp_ty(&field.typ, false), ")", )), }; self.state.pop_state(old, StateElem::Label(&lab)); res } - fn pp_variant_fields<'b>(&mut self, fs: &'b [Field]) -> RcDoc<'b> { + fn pp_variant_fields<'b>(&mut self, fs: &'b [TypeField]) -> RcDoc<'b> { let old = self.state.push_state(&StateElem::TypeStr("variant")); let fields: Vec<_> = fs.iter().map(|f| self.pp_variant_field(f)).collect(); let fields = concat(fields.into_iter(), ","); @@ -424,9 +446,9 @@ fn test_{test_name}() {{ .clone() .map(RcDoc::text) .unwrap_or(RcDoc::text("#[derive(CandidType, Deserialize)]")); - let line = match ty.as_ref() { - TypeInner::Record(fs) => { - let separator = if is_tuple(fs) { + let line = match ty { + IDLType::RecordT(fs) => { + let separator = if is_tuple(ty) { RcDoc::text(";") } else { RcDoc::nil() @@ -440,7 +462,7 @@ fn test_{test_name}() {{ .append(self.pp_record_fields(fs, true, false)) .append(separator) } - TypeInner::Variant(fs) => { + IDLType::VariantT(fs) => { if as_result(fs).is_some() { vis.append(kwd("type")) .append(name) @@ -457,13 +479,13 @@ fn test_{test_name}() {{ .append(self.pp_variant_fields(fs)) } } - TypeInner::Func(func) => str("candid::define_function!(") + IDLType::FuncT(func) => str("candid::define_function!(") .append(vis) .append(name) .append(" : ") .append(self.pp_ty_func(func)) .append(");"), - TypeInner::Service(serv) => str("candid::define_service!(") + IDLType::ServT(serv) => str("candid::define_service!(") .append(vis) .append(name) .append(" : ") @@ -493,7 +515,7 @@ fn test_{test_name}() {{ } lines(res.into_iter()) } - fn pp_args<'b>(&mut self, args: &'b [ArgType], prefix: &'b str) -> RcDoc<'b> { + fn pp_args<'b>(&mut self, args: &'b [IDLArgType], prefix: &'b str) -> RcDoc<'b> { let doc: Vec<_> = args .iter() .enumerate() @@ -508,7 +530,7 @@ fn test_{test_name}() {{ let doc = concat(doc.into_iter(), ","); enclose("(", doc, ")") } - fn pp_rets<'b>(&mut self, args: &'b [Type], prefix: &'b str) -> RcDoc<'b> { + fn pp_rets<'b>(&mut self, args: &'b [IDLType], prefix: &'b str) -> RcDoc<'b> { let doc: Vec<_> = args .iter() .enumerate() @@ -523,7 +545,7 @@ fn test_{test_name}() {{ let doc = concat(doc.into_iter(), ","); enclose("(", doc, ")") } - fn pp_ty_func<'b>(&mut self, f: &'b Function) -> RcDoc<'b> { + fn pp_ty_func<'b>(&mut self, f: &'b FuncType) -> RcDoc<'b> { let lab = StateElem::TypeStr("func"); let old = self.state.push_state(&lab); let args = self.pp_args(&f.args, "arg"); @@ -537,19 +559,19 @@ fn test_{test_name}() {{ self.state.pop_state(old, lab); res } - fn pp_ty_service<'b>(&mut self, serv: &'b [(String, Type)]) -> RcDoc<'b> { + fn pp_ty_service<'b>(&mut self, serv: &'b [Binding]) -> RcDoc<'b> { let lab = StateElem::TypeStr("service"); let old = self.state.push_state(&lab); let mut list = Vec::new(); - for (id, func) in serv.iter() { - let func_doc = match func.as_ref() { - TypeInner::Func(ref f) => enclose("candid::func!(", self.pp_ty_func(f), ")"), - TypeInner::Var(_) => self.pp_ty(func, true).append("::ty()"), + for binding in serv.iter() { + let func_doc = match &binding.typ { + IDLType::FuncT(ref f) => enclose("candid::func!(", self.pp_ty_func(f), ")"), + IDLType::VarT(_) => self.pp_ty(&binding.typ, true).append("::ty()"), _ => unreachable!(), }; list.push( RcDoc::text("\"") - .append(id) + .append(&binding.id) .append(kwd("\" :")) .append(func_doc), ); @@ -559,7 +581,7 @@ fn test_{test_name}() {{ self.state.pop_state(old, lab); res } - fn pp_function(&mut self, id: &str, func: &Function) -> Method { + fn pp_function(&mut self, id: &str, func: &FuncType) -> Method { use candid::types::internal::FuncMode; let old = self.state.push_state(&StateElem::Label(id)); let name = self @@ -623,9 +645,9 @@ fn test_{test_name}() {{ self.state.pop_state(old, StateElem::Label(id)); res } - fn pp_actor(&mut self, actor: &Type) -> (Vec, Option>) { + fn pp_actor(&mut self, actor: &IDLType) -> (Vec, Option>) { let actor = self.state.env.trace_type(actor).unwrap(); - let init = if let TypeInner::Class(args, _) = actor.as_ref() { + let init = if let IDLType::ClassT(args, _) = &actor { let old = self.state.push_state(&StateElem::Label("init")); let args: Vec<_> = args .iter() @@ -652,9 +674,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 func = self.state.env.as_func(func).unwrap(); - res.push(self.pp_function(id, func)); + for binding in serv.iter() { + let func = self.state.env.as_func(&binding.typ).unwrap(); + res.push(self.pp_function(&binding.id, func)); } (res, init) } @@ -674,16 +696,16 @@ pub struct Method { pub rets: Vec, pub mode: String, } -pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Output, Vec) { +pub fn emit_bindgen(tree: &Config, env: &IDLEnv) -> (Output, Vec) { let mut state = NominalState { state: crate::configs::State::new(&tree.0, env), }; - let (env, actor) = state.nominalize_all(actor); + let env = state.nominalize_all(); let old_stats = state.state.stats.clone(); - let def_list = if let Some(actor) = &actor { - chase_actor(&env, actor).unwrap() + let def_list = if env.actor.is_some() { + chase_actor(&env).unwrap() } else { - env.0.iter().map(|pair| pair.0.as_ref()).collect::>() + env.bindings_ids() }; let recs = infer_rec(&env, &def_list).unwrap(); let mut state = State { @@ -693,7 +715,7 @@ pub fn emit_bindgen(tree: &Config, env: &TypeEnv, actor: &Option) -> (Outp }; state.state.stats = old_stats; let defs = state.pp_defs(&def_list); - let (methods, init_args) = if let Some(actor) = &actor { + let (methods, init_args) = if let Some(actor) = &env.actor { state.pp_actor(actor) } else { (Vec::new(), None) @@ -752,17 +774,12 @@ impl Default for ExternalConfig { ) } } -pub fn compile( - tree: &Config, - env: &TypeEnv, - actor: &Option, - mut external: ExternalConfig, -) -> (String, Vec) { +pub fn compile(tree: &Config, env: &IDLEnv, mut external: ExternalConfig) -> (String, Vec) { let source = match external.0.get("target").map(|s| s.as_str()) { Some("canister_call") | None => Cow::Borrowed(include_str!("rust_call.hbs")), Some("agent") => Cow::Borrowed(include_str!("rust_agent.hbs")), Some("stub") => { - let metadata = crate::utils::get_metadata(env, actor); + let metadata = crate::utils::get_metadata(env); if let Some(metadata) = metadata { external.0.insert("metadata".to_string(), metadata); } @@ -777,7 +794,7 @@ pub fn compile( } _ => unimplemented!(), }; - let (output, unused) = emit_bindgen(tree, env, actor); + let (output, unused) = emit_bindgen(tree, env); (output_handlebar(output, external, &source), unused) } @@ -812,47 +829,50 @@ struct NominalState<'a> { } impl NominalState<'_> { // Convert structural typing to nominal typing to fit Rust's type system - fn nominalize(&mut self, env: &mut TypeEnv, path: &mut Vec, t: &Type) -> Type { + fn nominalize(&mut self, env: &mut IDLEnv, path: &mut Vec, t: &IDLType) -> IDLType { let elem = StateElem::Type(t); - let old = if matches!(t.as_ref(), TypeInner::Func(_)) { + let old = if matches!(t, IDLType::FuncT(_)) { // strictly speaking, we want to avoid func label from the main service. But this is probably good enough. None } else { Some(self.state.push_state(&elem)) }; - let res = match t.as_ref() { - TypeInner::Opt(ty) => { + let res = match t { + IDLType::OptT(ty) => { path.push(TypePath::Opt); let ty = self.nominalize(env, path, ty); path.pop(); - TypeInner::Opt(ty) + IDLType::OptT(Box::new(ty)) } - TypeInner::Vec(ty) => { + IDLType::VecT(ty) => { path.push(TypePath::Vec); let ty = self.nominalize(env, path, ty); path.pop(); - TypeInner::Vec(ty) + IDLType::VecT(Box::new(ty)) } - TypeInner::Record(fs) => { + IDLType::RecordT(fs) => { if matches!( path.last(), None | Some(TypePath::VariantField(_)) | Some(TypePath::Id(_)) - ) || is_tuple(fs) + ) || is_tuple(t) { let fs: Vec<_> = fs .iter() - .map(|Field { id, ty }| { - let lab = id.to_string(); + .map(|TypeField { label, typ }| { + let lab = label.to_string(); let elem = StateElem::Label(&lab); let old = self.state.push_state(&elem); - path.push(TypePath::RecordField(id.to_string())); - let ty = self.nominalize(env, path, ty); + path.push(TypePath::RecordField(lab.clone())); + let ty = self.nominalize(env, path, typ); path.pop(); self.state.pop_state(old, elem); - Field { id: id.clone(), ty } + TypeField { + label: label.clone(), + typ: ty, + } }) .collect(); - TypeInner::Record(fs) + IDLType::RecordT(fs) } else { let new_var = if let Some(name) = &self.state.config.name { let res = name.to_string(); @@ -864,33 +884,39 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Record(fs.to_vec()).into(), + &IDLType::RecordT(fs.to_vec()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } } - TypeInner::Variant(fs) => { + IDLType::VariantT(fs) => { let is_result = as_result(fs).is_some(); if matches!(path.last(), None | Some(TypePath::Id(_))) || is_result { let fs: Vec<_> = fs .iter() - .map(|Field { id, ty }| { - let lab = id.to_string(); + .map(|TypeField { label, typ }| { + let lab = label.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); if is_result { // so that inner record gets a new name - path.push(TypePath::ResultField(id.to_string())); + path.push(TypePath::ResultField(lab.clone())); } else { - path.push(TypePath::VariantField(id.to_string())); + path.push(TypePath::VariantField(lab.clone())); } - let ty = self.nominalize(env, path, ty); + let ty = self.nominalize(env, path, typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - Field { id: id.clone(), ty } + TypeField { + label: label.clone(), + typ: ty, + } }) .collect(); - TypeInner::Variant(fs) + IDLType::VariantT(fs) } else { let new_var = if let Some(name) = &self.state.config.name { let res = name.to_string(); @@ -902,16 +928,19 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Variant(fs.to_vec()).into(), + &IDLType::VariantT(fs.to_vec()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } } - TypeInner::Func(func) => match path.last() { + IDLType::FuncT(func) => match path.last() { None | Some(TypePath::Id(_)) => { let func = func.clone(); - TypeInner::Func(Function { + IDLType::FuncT(FuncType { modes: func.modes, args: func .args @@ -929,7 +958,7 @@ impl NominalState<'_> { let ty = self.nominalize(env, path, &arg.typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - ArgType { + IDLArgType { name: arg.name.clone(), typ: ty, } @@ -967,23 +996,26 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Func(func.clone()).into(), + &IDLType::FuncT(func.clone()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } }, - TypeInner::Service(serv) => match path.last() { - None | Some(TypePath::Id(_)) => TypeInner::Service( + IDLType::ServT(serv) => match path.last() { + None | Some(TypePath::Id(_)) => IDLType::ServT( serv.iter() - .map(|(meth, ty)| { - let lab = meth.to_string(); + .map(|Binding { id, typ }| { + let lab = id.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); - path.push(TypePath::Id(meth.to_string())); - let ty = self.nominalize(env, path, ty); + path.push(TypePath::Id(lab.clone())); + let ty = self.nominalize(env, path, &typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); - (meth.clone(), ty) + Binding { id: lab, typ: ty } }) .collect(), ), @@ -998,13 +1030,16 @@ impl NominalState<'_> { let ty = self.nominalize( env, &mut vec![TypePath::Id(new_var.clone())], - &TypeInner::Service(serv.clone()).into(), + &IDLType::ServT(serv.clone()), ); - env.0.insert(new_var.clone(), ty); - TypeInner::Var(new_var) + env.insert_binding(Binding { + id: new_var.clone(), + typ: ty, + }); + IDLType::VarT(new_var) } }, - TypeInner::Class(args, ty) => TypeInner::Class( + IDLType::ClassT(args, ty) => IDLType::ClassT( args.iter() .map(|arg| { let elem = StateElem::Label("init"); @@ -1013,36 +1048,46 @@ impl NominalState<'_> { let ty = self.nominalize(env, path, &arg.typ); path.pop(); self.state.pop_state(old, elem); - ArgType { + IDLArgType { name: arg.name.clone(), typ: ty, } }) .collect(), - self.nominalize(env, path, ty), + Box::new(self.nominalize(env, path, ty)), ), t => t.clone(), - } - .into(); + }; if let Some(old) = old { self.state.pop_state(old, elem); } res } - fn nominalize_all(&mut self, actor: &Option) -> (TypeEnv, Option) { - let mut res = TypeEnv(Default::default()); - for (id, ty) in self.state.env.0.iter() { - let elem = StateElem::Label(id); + fn nominalize_all(&mut self) -> IDLEnv { + let mut res = IDLEnv::new(); + for binding in self.state.env.types_bindings.iter() { + let elem = StateElem::Label(binding.id.as_str()); let old = self.state.push_state(&elem); - let ty = self.nominalize(&mut res, &mut vec![TypePath::Id(id.clone())], ty); - res.0.insert(id.to_string(), ty); + let ty = self.nominalize( + &mut res, + &mut vec![TypePath::Id(binding.id.clone())], + &binding.typ, + ); + res.insert_binding(Binding { + id: binding.id.clone(), + typ: ty, + }); self.state.pop_state(old, elem); } - let actor = actor + let actor = self + .state + .env + .actor .as_ref() .map(|ty| self.nominalize(&mut res, &mut vec![], ty)); - (res, actor) + res.set_actor(actor); + res } } fn get_hbs() -> handlebars::Handlebars<'static> { diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 5a4460bf2..641a32166 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -1,11 +1,14 @@ use super::javascript::{ident, is_tuple}; use candid::pretty::utils::*; -use candid::types::{Field, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use candid::types::{ + syntax::{Binding, FuncType, IDLEnv, IDLType, PrimType, TypeField}, + Label, +}; use pretty::RcDoc; -fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { - use TypeInner::*; - match ty.as_ref() { +fn pp_prim_ty(ty: &PrimType) -> RcDoc { + use PrimType::*; + match ty { Null => str("null"), Bool => str("boolean"), Nat => str("bigint"), @@ -23,10 +26,17 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { Text => str("string"), Reserved => str("any"), Empty => str("never"), - Var(ref id) => { + } +} + +fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { + use IDLType::*; + match ty { + PrimT(ref ty) => pp_prim_ty(ty), + VarT(ref id) => { if is_ref { let ty = env.rec_find_type(id).unwrap(); - if matches!(ty.as_ref(), Service(_) | Func(_)) { + if matches!(ty, ServT(_) | FuncT(_)) { pp_ty(env, ty, false) } else { ident(id) @@ -35,15 +45,22 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { ident(id) } } - Principal => str("Principal"), - Opt(ref t) => str("[] | ").append(enclose("[", pp_ty(env, t, is_ref), "]")), - Vec(ref t) => { + PrincipalT => str("Principal"), + OptT(ref t) => str("[] | ").append(enclose("[", pp_ty(env, t, is_ref), "]")), + VecT(ref t) => { let ty = match t.as_ref() { - Var(ref id) => { + VarT(ref id) => { let ty = env.rec_find_type(id).unwrap(); if matches!( - ty.as_ref(), - Nat8 | Nat16 | Nat32 | Nat64 | Int8 | Int16 | Int32 | Int64 + ty, + PrimT(PrimType::Nat8) + | PrimT(PrimType::Nat16) + | PrimT(PrimType::Nat32) + | PrimT(PrimType::Nat64) + | PrimT(PrimType::Int8) + | PrimT(PrimType::Int16) + | PrimT(PrimType::Int32) + | PrimT(PrimType::Int64) ) { ty } else { @@ -52,28 +69,28 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { } _ => t, }; - match ty.as_ref() { - Nat8 => str("Uint8Array | number[]"), - Nat16 => str("Uint16Array | number[]"), - Nat32 => str("Uint32Array | number[]"), - Nat64 => str("BigUint64Array | bigint[]"), - Int8 => str("Int8Array | number[]"), - Int16 => str("Int16Array | number[]"), - Int32 => str("Int32Array | number[]"), - Int64 => str("BigInt64Array | bigint[]"), + match ty { + PrimT(PrimType::Nat8) => str("Uint8Array | number[]"), + PrimT(PrimType::Nat16) => str("Uint16Array | number[]"), + PrimT(PrimType::Nat32) => str("Uint32Array | number[]"), + PrimT(PrimType::Nat64) => str("BigUint64Array | bigint[]"), + PrimT(PrimType::Int8) => str("Int8Array | number[]"), + PrimT(PrimType::Int16) => str("Int16Array | number[]"), + PrimT(PrimType::Int32) => str("Int32Array | number[]"), + PrimT(PrimType::Int64) => str("BigInt64Array | bigint[]"), _ => str("Array").append(enclose("<", pp_ty(env, t, is_ref), ">")), } } - Record(ref fs) => { + RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(env, &f.ty, is_ref)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(env, &f.typ, is_ref)), ","); enclose("[", tuple, "]") } else { let fields = concat(fs.iter().map(|f| pp_field(env, f, is_ref)), ","); enclose_space("{", fields, "}") } } - Variant(ref fs) => { + VariantT(ref fs) => { if fs.is_empty() { str("never") } else { @@ -85,15 +102,14 @@ fn pp_ty<'a>(env: &'a TypeEnv, ty: &'a Type, is_ref: bool) -> RcDoc<'a> { .nest(INDENT_SPACE) } } - Func(_) => str("[Principal, string]"), - Service(_) => str("Principal"), - Class(_, _) => unreachable!(), - Knot(_) | Unknown | Future => unreachable!(), + FuncT(_) => str("[Principal, string]"), + ServT(_) => str("Principal"), + ClassT(_, _) => unreachable!(), } } -fn pp_label(id: &SharedLabel) -> RcDoc { - match &**id { +fn pp_label(id: &Label) -> RcDoc { + match id { Label::Named(str) => quote_ident(str), Label::Id(n) | Label::Unnamed(n) => str("_") .append(RcDoc::as_string(n)) @@ -102,13 +118,13 @@ fn pp_label(id: &SharedLabel) -> RcDoc { } } -fn pp_field<'a>(env: &'a TypeEnv, field: &'a Field, is_ref: bool) -> RcDoc<'a> { - pp_label(&field.id) +fn pp_field<'a>(env: &'a IDLEnv, field: &'a TypeField, is_ref: bool) -> RcDoc<'a> { + pp_label(&field.label) .append(kwd(":")) - .append(pp_ty(env, &field.ty, is_ref)) + .append(pp_ty(env, &field.typ, is_ref)) } -fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { +fn pp_function<'a>(env: &'a IDLEnv, func: &'a FuncType) -> RcDoc<'a> { let args = func.args.iter().map(|arg| pp_ty(env, &arg.typ, true)); let args = enclose("[", concat(args, ","), "]"); let rets = match func.rets.len() { @@ -127,12 +143,12 @@ fn pp_function<'a>(env: &'a TypeEnv, func: &'a Function) -> RcDoc<'a> { ) } -fn pp_service<'a>(env: &'a TypeEnv, serv: &'a [(String, Type)]) -> RcDoc<'a> { +fn pp_service<'a>(env: &'a IDLEnv, serv: &'a [Binding]) -> RcDoc<'a> { let doc = concat( - serv.iter().map(|(id, func)| { - let func = match func.as_ref() { - TypeInner::Func(ref func) => pp_function(env, func), - _ => pp_ty(env, func, false), + serv.iter().map(|Binding { id, typ }| { + let func = match typ { + IDLType::FuncT(ref func) => pp_function(env, func), + _ => pp_ty(env, typ, false), }; quote_ident(id).append(kwd(":")).append(func) }), @@ -141,19 +157,19 @@ fn pp_service<'a>(env: &'a TypeEnv, serv: &'a [(String, Type)]) -> RcDoc<'a> { enclose_space("{", doc, "}") } -fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { +fn pp_defs<'a>(env: &'a IDLEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { lines(def_list.iter().map(|id| { let ty = env.find_type(id).unwrap(); - let export = match ty.as_ref() { - TypeInner::Record(_) if !ty.is_tuple() => kwd("export interface") + let export = match ty { + IDLType::RecordT(_) if !ty.is_tuple() => kwd("export interface") .append(ident(id)) .append(" ") .append(pp_ty(env, ty, false)), - TypeInner::Service(ref serv) => kwd("export interface") + IDLType::ServT(ref serv) => kwd("export interface") .append(ident(id)) .append(" ") .append(pp_service(env, serv)), - TypeInner::Func(ref func) => kwd("export type") + IDLType::FuncT(ref func) => kwd("export type") .append(ident(id)) .append(" = ") .append(pp_function(env, func)) @@ -168,27 +184,29 @@ fn pp_defs<'a>(env: &'a TypeEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { })) } -fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type) -> RcDoc<'a> { - match ty.as_ref() { - TypeInner::Service(ref serv) => { - kwd("export interface _SERVICE").append(pp_service(env, serv)) - } - TypeInner::Var(id) => kwd("export interface _SERVICE extends") +fn pp_actor<'a>(env: &'a IDLEnv, ty: &'a IDLType) -> RcDoc<'a> { + match ty { + IDLType::ServT(ref serv) => kwd("export interface _SERVICE").append(pp_service(env, serv)), + IDLType::VarT(id) => kwd("export interface _SERVICE extends") .append(str(id)) .append(str(" {}")), - TypeInner::Class(_, t) => pp_actor(env, t), + IDLType::ClassT(_, t) => pp_actor(env, t), _ => unreachable!(), } } -pub fn compile(env: &TypeEnv, actor: &Option) -> String { +pub fn compile(env: &IDLEnv) -> String { let header = r#"import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; "#; - let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); + let def_list: Vec<_> = env + .types_bindings + .iter() + .map(|Binding { id, typ: _ }| id.as_ref()) + .collect(); let defs = pp_defs(env, &def_list); - let actor = match actor { + let actor = match &env.actor { None => RcDoc::nil(), Some(actor) => pp_actor(env, actor) .append(RcDoc::line()) diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 4b6a57cde..c25903566 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use candid::types::{Type, TypeEnv, TypeInner}; +use candid::types::syntax::{IDLEnv, IDLType, PrimType}; use serde::de::DeserializeOwned; use std::collections::{BTreeMap, BTreeSet}; use toml::{Table, Value}; @@ -12,7 +12,7 @@ pub struct State<'a, T: ConfigState> { pub stats: BTreeMap, u32>, pub config: T, pub config_source: BTreeMap>, - pub env: &'a TypeEnv, + pub env: &'a IDLEnv, } pub struct ConfigBackup { config: T, @@ -20,7 +20,7 @@ pub struct ConfigBackup { } #[derive(Debug)] pub enum StateElem<'a> { - Type(&'a Type), + Type(&'a IDLType), TypeStr(&'a str), Label(&'a str), } @@ -36,7 +36,7 @@ pub enum ScopePos { } impl<'a, T: ConfigState> State<'a, T> { - pub fn new(tree: &'a ConfigTree, env: &'a TypeEnv) -> Self { + pub fn new(tree: &'a ConfigTree, env: &'a IDLEnv) -> Self { let mut config = T::default(); let mut config_source = BTreeMap::new(); if let Some(state) = &tree.state { @@ -374,38 +374,40 @@ fn generate_state_tree(v: Value) -> Result> { Err(anyhow::anyhow!("Expected a table")) } } -fn path_name(t: &Type) -> String { - match t.as_ref() { - TypeInner::Null => "null", - TypeInner::Bool => "bool", - TypeInner::Nat => "nat", - TypeInner::Int => "int", - TypeInner::Nat8 => "nat8", - TypeInner::Nat16 => "nat16", - TypeInner::Nat32 => "nat32", - TypeInner::Nat64 => "nat64", - TypeInner::Int8 => "int8", - TypeInner::Int16 => "int16", - TypeInner::Int32 => "int32", - TypeInner::Int64 => "int64", - TypeInner::Float32 => "float32", - TypeInner::Float64 => "float64", - TypeInner::Text => "text", - TypeInner::Reserved => "reserved", - TypeInner::Empty => "empty", - TypeInner::Var(id) => id, - TypeInner::Knot(id) => id.name, - TypeInner::Principal => "principal", - TypeInner::Opt(_) => "opt", - TypeInner::Vec(t) if matches!(t.as_ref(), TypeInner::Nat8) => "blob", - TypeInner::Vec(_) => "vec", - TypeInner::Record(_) => "record", - TypeInner::Variant(_) => "variant", - TypeInner::Func(_) => "func", - TypeInner::Service(_) => "service", - TypeInner::Future => "future", - TypeInner::Class(..) => "func:init", - TypeInner::Unknown => unreachable!(), +fn prim_path_name(t: &PrimType) -> &str { + match t { + PrimType::Null => "null", + PrimType::Bool => "bool", + PrimType::Nat => "nat", + PrimType::Int => "int", + PrimType::Nat8 => "nat8", + PrimType::Nat16 => "nat16", + PrimType::Nat32 => "nat32", + PrimType::Nat64 => "nat64", + PrimType::Int8 => "int8", + PrimType::Int16 => "int16", + PrimType::Int32 => "int32", + PrimType::Int64 => "int64", + PrimType::Float32 => "float32", + PrimType::Float64 => "float64", + PrimType::Text => "text", + PrimType::Reserved => "reserved", + PrimType::Empty => "empty", + } +} +fn path_name(t: &IDLType) -> String { + match t { + IDLType::PrimT(t) => prim_path_name(t), + IDLType::VarT(id) => id, + IDLType::PrincipalT => "principal", + IDLType::OptT(_) => "opt", + IDLType::VecT(t) if matches!(t.as_ref(), IDLType::PrimT(PrimType::Nat8)) => "blob", + IDLType::VecT(_) => "vec", + IDLType::RecordT(_) => "record", + IDLType::VariantT(_) => "variant", + IDLType::FuncT(_) => "func", + IDLType::ServT(_) => "service", + IDLType::ClassT(..) => "func:init", } .to_string() } @@ -520,7 +522,7 @@ Vec = { width = 2, size = 10 } t.clone(), ); assert_eq!(tree.max_depth, 4); - let env = TypeEnv::default(); + let env = IDLEnv::new(); let mut state = State::new(&tree, &env); state.with_scope( &Some(Scope { diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 8a38e586d..445fdd2e1 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -1,9 +1,9 @@ use super::configs::{ConfigState, Configs, Context, Scope, State, StateElem}; use crate::{Error, Result}; use arbitrary::{unstructured::Int, Arbitrary, Unstructured}; +use candid::types::syntax::{IDLEnv, IDLType, PrimType, TypeField}; use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; -use candid::types::{Field, Type, TypeEnv, TypeInner}; -use candid::Deserialize; +use candid::{Deserialize, TypeEnv}; use std::collections::HashSet; use std::convert::TryFrom; @@ -63,7 +63,7 @@ impl ConfigState for GenConfig { } fn update_state(&mut self, elem: &StateElem) { if let StateElem::Type(t) = elem { - if !matches!(t.as_ref(), TypeInner::Var(_)) { + if !matches!(t, IDLType::VarT(_)) { self.depth = self.depth.map(|d| d - 1); self.size = self.size.map(|s| s - 1); } @@ -71,7 +71,7 @@ impl ConfigState for GenConfig { } fn restore_state(&mut self, elem: &StateElem) { if let StateElem::Type(t) = elem { - if !matches!(t.as_ref(), TypeInner::Var(_)) { + if !matches!(t, IDLType::VarT(_)) { self.depth = self.depth.map(|d| d + 1); } } @@ -112,67 +112,69 @@ impl ConfigState for GenConfig { pub struct RandState<'a>(State<'a, GenConfig>); impl RandState<'_> { - pub fn any(&mut self, u: &mut Unstructured, ty: &Type) -> Result { + pub fn any(&mut self, u: &mut Unstructured, ty: &IDLType) -> Result { let old_config = self.0.push_state(&StateElem::Type(ty)); if let Some(vec) = &self.0.config.value { let v = u.choose(vec)?; let v: IDLValue = super::parse_idl_value(v)?; - let v = v.annotate_type(true, self.0.env, ty)?; + let env = TypeEnv::new(); + let t = crate::typing::ast_to_type(&env, ty).unwrap(); + let v = v.annotate_type(true, &env, &t)?; self.0.pop_state(old_config, StateElem::Type(ty)); self.0.update_stats("value"); return Ok(v); } - let res = Ok(match ty.as_ref() { - TypeInner::Var(id) => { - let ty = self.0.env.rec_find_type(id)?; + let res = Ok(match ty { + IDLType::VarT(id) => { + let ty = self.0.env.rec_find_type(id).map_err(|e| Error::msg(e))?; self.any(u, ty)? } - TypeInner::Null => IDLValue::Null, - TypeInner::Reserved => IDLValue::Reserved, - TypeInner::Bool => IDLValue::Bool(u.arbitrary()?), - TypeInner::Int => { + IDLType::PrimT(PrimType::Null) => IDLValue::Null, + IDLType::PrimT(PrimType::Reserved) => IDLValue::Reserved, + IDLType::PrimT(PrimType::Bool) => IDLValue::Bool(u.arbitrary()?), + IDLType::PrimT(PrimType::Int) => { self.0.update_stats("range"); IDLValue::Int(arbitrary_num::(u, self.0.config.range)?.into()) } - TypeInner::Nat => { + IDLType::PrimT(PrimType::Nat) => { self.0.update_stats("range"); IDLValue::Nat(arbitrary_num::(u, self.0.config.range)?.into()) } - TypeInner::Nat8 => { + IDLType::PrimT(PrimType::Nat8) => { self.0.update_stats("range"); IDLValue::Nat8(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Nat16 => { + IDLType::PrimT(PrimType::Nat16) => { self.0.update_stats("range"); IDLValue::Nat16(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Nat32 => { + IDLType::PrimT(PrimType::Nat32) => { self.0.update_stats("range"); IDLValue::Nat32(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Nat64 => { + IDLType::PrimT(PrimType::Nat64) => { self.0.update_stats("range"); IDLValue::Nat64(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int8 => { + IDLType::PrimT(PrimType::Int8) => { self.0.update_stats("range"); IDLValue::Int8(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int16 => { + IDLType::PrimT(PrimType::Int16) => { self.0.update_stats("range"); IDLValue::Int16(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int32 => { + IDLType::PrimT(PrimType::Int32) => { self.0.update_stats("range"); IDLValue::Int32(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Int64 => { + IDLType::PrimT(PrimType::Int64) => { self.0.update_stats("range"); IDLValue::Int64(arbitrary_num(u, self.0.config.range)?) } - TypeInner::Float32 => IDLValue::Float32(u.arbitrary()?), - TypeInner::Float64 => IDLValue::Float64(u.arbitrary()?), - TypeInner::Text => { + IDLType::PrimT(PrimType::Float32) => IDLValue::Float32(u.arbitrary()?), + IDLType::PrimT(PrimType::Float64) => IDLValue::Float64(u.arbitrary()?), + IDLType::PrimT(PrimType::Text) => { self.0.update_stats("text"); self.0.update_stats("width"); IDLValue::Text(arbitrary_text( @@ -181,7 +183,7 @@ impl RandState<'_> { &self.0.config.width, )?) } - TypeInner::Opt(t) => { + IDLType::OptT(t) => { self.0.update_stats("depth"); self.0.update_stats("size"); let depths = if self.0.config.depth.is_some_and(|d| d <= 0) @@ -198,7 +200,7 @@ impl RandState<'_> { IDLValue::Opt(Box::new(self.any(u, t)?)) } } - TypeInner::Vec(t) => { + IDLType::VecT(t) => { self.0.update_stats("width"); let width = self.0.config.width.or_else(|| { let elem_size = size(self.0.env, t).unwrap_or(MAX_DEPTH); @@ -213,25 +215,25 @@ impl RandState<'_> { } IDLValue::Vec(vec) } - TypeInner::Record(fs) => { + IDLType::RecordT(fs) => { let mut res = Vec::new(); - for Field { id, ty } in fs.iter() { - let lab_str = id.to_string(); + for TypeField { label, typ } in fs.iter() { + let lab_str = label.to_string(); let elem = StateElem::Label(&lab_str); let old_config = self.0.push_state(&elem); - let val = self.any(u, ty)?; + let val = self.any(u, typ)?; self.0.pop_state(old_config, elem); res.push(IDLField { - id: id.as_ref().clone(), + id: label.clone(), val, }); } IDLValue::Record(res) } - TypeInner::Variant(fs) => { + IDLType::VariantT(fs) => { let choices = fs .iter() - .map(|Field { ty, .. }| size(self.0.env, ty).unwrap_or(MAX_DEPTH)); + .map(|TypeField { typ, .. }| size(self.0.env, typ).unwrap_or(MAX_DEPTH)); self.0.update_stats("size"); self.0.update_stats("depth"); let sizes: Vec<_> = if self.0.config.depth.is_some_and(|d| d <= 0) @@ -243,20 +245,20 @@ impl RandState<'_> { choices.collect() }; let idx = arbitrary_variant(u, &sizes)?; - let Field { id, ty } = &fs[idx]; - let lab_str = id.to_string(); + let TypeField { label, typ } = &fs[idx]; + let lab_str = label.to_string(); let elem = StateElem::Label(&lab_str); let old_config = self.0.push_state(&elem); - let val = self.any(u, ty)?; + let val = self.any(u, typ)?; self.0.pop_state(old_config, elem); let field = IDLField { - id: id.as_ref().clone(), + id: label.clone(), val, }; IDLValue::Variant(VariantValue(Box::new(field), idx as u64)) } - TypeInner::Principal => IDLValue::Principal(crate::Principal::arbitrary(u)?), - TypeInner::Func(_) => { + IDLType::PrincipalT => IDLValue::Principal(crate::Principal::arbitrary(u)?), + IDLType::FuncT(_) => { self.0.update_stats("text"); self.0.update_stats("width"); IDLValue::Func( @@ -264,7 +266,7 @@ impl RandState<'_> { arbitrary_text(u, &self.0.config.text, &self.0.config.width)?, ) } - TypeInner::Service(_) => IDLValue::Service(crate::Principal::arbitrary(u)?), + IDLType::ServT(_) => IDLValue::Service(crate::Principal::arbitrary(u)?), _ => unimplemented!(), }); self.0.pop_state(old_config, StateElem::Type(ty)); @@ -275,8 +277,8 @@ impl RandState<'_> { pub fn any( seed: &[u8], configs: Configs, - env: &TypeEnv, - types: &[Type], + env: &IDLEnv, + types: &[IDLType], scope: &Option, ) -> Result { let mut u = arbitrary::Unstructured::new(seed); @@ -293,10 +295,10 @@ pub fn any( Ok(IDLArgs { args }) } -fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option { - use TypeInner::*; - Some(match t.as_ref() { - Var(id) => { +fn size_helper(env: &IDLEnv, seen: &mut HashSet, t: &IDLType) -> Option { + use IDLType::*; + Some(match t { + VarT(id) => { if seen.insert(id.to_string()) { let ty = env.rec_find_type(id).unwrap(); let res = size_helper(env, seen, ty)?; @@ -306,20 +308,20 @@ fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option 0, - Opt(t) => 1 + size_helper(env, seen, t)?, - Vec(t) => 1 + size_helper(env, seen, t)? * 2, - Record(fs) => { + PrimT(PrimType::Empty) => 0, + OptT(t) => 1 + size_helper(env, seen, t)?, + VecT(t) => 1 + size_helper(env, seen, t)? * 2, + RecordT(fs) => { let mut sum = 0; - for Field { ty, .. } in fs.iter() { - sum += size_helper(env, seen, ty)?; + for TypeField { typ, .. } in fs.iter() { + sum += size_helper(env, seen, typ)?; } 1 + sum } - Variant(fs) => { + VariantT(fs) => { let mut max = 0; - for Field { ty, .. } in fs.iter() { - let s = size_helper(env, seen, ty)?; + for TypeField { typ, .. } in fs.iter() { + let s = size_helper(env, seen, typ)?; if s > max { max = s; }; @@ -330,7 +332,7 @@ fn size_helper(env: &TypeEnv, seen: &mut HashSet, t: &Type) -> Option Option { +fn size(env: &IDLEnv, t: &IDLType) -> Option { let mut seen = HashSet::new(); size_helper(env, &mut seen, t) } diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index 9f3dbd970..c5d9d9391 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -34,10 +34,10 @@ pub struct HostTest { } pub enum HostAssert { // The encoded bytes is not unique - Encode(IDLArgs, Vec, bool, Vec), - NotEncode(IDLArgs, Vec), - Decode(Vec, Vec, bool, IDLArgs), - NotDecode(Vec, Vec), + Encode(IDLArgs, Vec<(IDLType, Type)>, bool, Vec), + NotEncode(IDLArgs, Vec<(IDLType, Type)>), + Decode(Vec, Vec<(IDLType, Type)>, bool, IDLArgs), + NotDecode(Vec, Vec<(IDLType, Type)>), } impl Assert { @@ -83,7 +83,7 @@ impl std::str::FromStr for Test { } impl HostTest { - pub fn from_assert(assert: &Assert, env: &TypeEnv, types: &[Type]) -> Self { + pub fn from_assert(assert: &Assert, env: &TypeEnv, types: &[(IDLType, Type)]) -> Self { use HostAssert::*; let types = types.to_vec(); let mut asserts = Vec::new(); @@ -100,10 +100,10 @@ impl HostTest { if !assert.pass && assert.right.is_none() { asserts.push(NotEncode(parsed, types)); } else { - let bytes = parsed.to_bytes_with_types(env, &types).unwrap(); + let bytes = parsed.to_bytes_with_types(env, &to_types(&types)).unwrap(); asserts.push(Encode(parsed.clone(), types.clone(), true, bytes.clone())); // round tripping - let vals = parsed.annotate_types(true, env, &types).unwrap(); + let vals = parsed.annotate_types(true, env, &to_types(&types)).unwrap(); asserts.push(Decode(bytes, types, true, vals)); } let desc = format!("(encode?) {}", assert.desc()); @@ -116,14 +116,18 @@ impl HostTest { } else { let mut config = DecoderConfig::new(); config.set_decoding_quota(DECODING_COST); - let args = - IDLArgs::from_bytes_with_types_with_config(&bytes, env, &types, &config) - .unwrap(); + let args = IDLArgs::from_bytes_with_types_with_config( + &bytes, + env, + &to_types(&types), + &config, + ) + .unwrap(); asserts.push(Decode(bytes.clone(), types.clone(), true, args)); // round tripping // asserts.push(Encode(args, types.clone(), true, bytes.clone())); if let Some(right) = &assert.right { - let expected = right.parse(env, &types).unwrap(); + let expected = right.parse(env, &to_types(&types)).unwrap(); if let Input::Blob(blob) = right { asserts.push(Decode( blob.to_vec(), @@ -198,3 +202,11 @@ pub fn check(test: Test) -> Result<()> { ))) } } + +pub fn to_idl_types(types: &[(IDLType, Type)]) -> Vec { + types.iter().map(|(idl_typ, _)| idl_typ.clone()).collect() +} + +fn to_types(types: &[(IDLType, Type)]) -> Vec { + types.iter().map(|(_, ty)| ty.clone()).collect() +} diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 0caca7dce..f6f9b0a12 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,7 +1,8 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ syntax::{ - Binding, Dec, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, PrimType, TypeField, + Binding, Dec, FuncType, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, PrimType, + TypeField, }, ArgType, Field, Function, Type, TypeEnv, TypeInner, }; @@ -16,13 +17,7 @@ pub struct Env<'a> { } impl Env<'_> { - fn insert(&mut self, id: String, t: Type) -> bool { - let duplicate = self.te.0.insert(id, t); - duplicate.is_some() - } - - fn insert_binding(&mut self, binding: Binding, t: Type) { - self.insert(binding.id.to_string(), t); + fn insert_binding(&mut self, binding: Binding) { self.idl_env.insert_binding(binding); } } @@ -34,10 +29,10 @@ pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { idl_env: &mut IDLEnv::new(), pre: false, }; - check_type(&env, ast) + map_type(&env, ast) } -fn check_prim(prim: &PrimType) -> Type { +fn map_prim(prim: &PrimType) -> Type { match prim { PrimType::Nat => TypeInner::Nat, PrimType::Nat8 => TypeInner::Nat8, @@ -60,38 +55,38 @@ fn check_prim(prim: &PrimType) -> Type { .into() } -pub fn check_type(env: &Env, t: &IDLType) -> Result { +pub fn map_type(env: &Env, t: &IDLType) -> Result { match t { - IDLType::PrimT(prim) => Ok(check_prim(prim)), + IDLType::PrimT(prim) => Ok(map_prim(prim)), IDLType::VarT(id) => { env.te.find_type(id)?; Ok(TypeInner::Var(id.to_string()).into()) } IDLType::OptT(t) => { - let t = check_type(env, t)?; + let t = map_type(env, t)?; Ok(TypeInner::Opt(t).into()) } IDLType::VecT(t) => { - let t = check_type(env, t)?; + let t = map_type(env, t)?; Ok(TypeInner::Vec(t).into()) } IDLType::RecordT(fs) => { - let fs = check_fields(env, fs)?; + let fs = map_fields(env, fs)?; Ok(TypeInner::Record(fs).into()) } IDLType::VariantT(fs) => { - let fs = check_fields(env, fs)?; + let fs = map_fields(env, fs)?; Ok(TypeInner::Variant(fs).into()) } IDLType::PrincipalT => Ok(TypeInner::Principal.into()), IDLType::FuncT(func) => { let mut t1 = Vec::new(); for arg in func.args.iter() { - t1.push(check_arg(env, arg)?); + t1.push(map_arg(env, arg)?); } let mut t2 = Vec::new(); for t in func.rets.iter() { - t2.push(check_type(env, t)?); + t2.push(map_type(env, t)?); } if func.modes.len() > 1 { return Err(Error::msg("cannot have more than one mode")); @@ -110,25 +105,25 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { Ok(TypeInner::Func(f).into()) } IDLType::ServT(ms) => { - let ms = check_meths(env, ms)?; + let ms = map_meths(env, ms)?; Ok(TypeInner::Service(ms).into()) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), } } -fn check_arg(env: &Env, arg: &IDLArgType) -> Result { +fn map_arg(env: &Env, arg: &IDLArgType) -> Result { Ok(ArgType { name: arg.name.clone(), - typ: check_type(env, &arg.typ)?, + typ: map_type(env, &arg.typ)?, }) } -fn check_fields(env: &Env, fs: &[TypeField]) -> Result> { +fn map_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 = map_type(env, &f.typ)?; let field = Field { id: f.label.clone().into(), ty, @@ -138,11 +133,11 @@ fn check_fields(env: &Env, fs: &[TypeField]) -> Result> { Ok(res) } -fn check_meths(env: &Env, ms: &[Binding]) -> Result> { +fn map_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 = map_type(env, &meth.typ)?; if !env.pre && env.te.as_func(&t).is_err() { return Err(Error::msg(format!( "method {} is a non-function type", @@ -154,12 +149,112 @@ fn check_meths(env: &Env, ms: &[Binding]) -> Result> { Ok(res) } +pub fn check_type(env: &Env, t: &IDLType) -> Result { + match t { + IDLType::PrimT(prim) => Ok(IDLType::PrimT(prim.clone())), + IDLType::VarT(id) => { + env.idl_env.find_type(id).map_err(Error::msg)?; + Ok(IDLType::VarT(id.to_string())) + } + IDLType::OptT(t) => { + let t = check_type(env, t)?; + Ok(IDLType::OptT(Box::new(t))) + } + IDLType::VecT(t) => { + let t = check_type(env, t)?; + Ok(IDLType::VecT(Box::new(t))) + } + IDLType::RecordT(fs) => { + let fs = check_fields(env, fs)?; + Ok(IDLType::RecordT(fs)) + } + IDLType::VariantT(fs) => { + let fs = check_fields(env, fs)?; + Ok(IDLType::VariantT(fs)) + } + IDLType::PrincipalT => Ok(IDLType::PrincipalT), + IDLType::FuncT(func) => { + let mut t1 = Vec::new(); + for arg in func.args.iter() { + t1.push(check_arg(env, arg)?); + } + let mut t2 = Vec::new(); + for t in func.rets.iter() { + t2.push(check_type(env, t)?); + } + if func.modes.len() > 1 { + return Err(Error::msg("cannot have more than one mode")); + } + if func.modes.len() == 1 + && func.modes[0] == candid::types::FuncMode::Oneway + && !t2.is_empty() + { + return Err(Error::msg("oneway function has non-unit return type")); + } + let f = FuncType { + modes: func.modes.clone(), + args: t1, + rets: t2, + }; + Ok(IDLType::FuncT(f)) + } + IDLType::ServT(ms) => { + let ms = check_meths(env, ms)?; + Ok(IDLType::ServT(ms)) + } + IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + } +} + +fn check_arg(env: &Env, arg: &IDLArgType) -> Result { + Ok(IDLArgType { + name: arg.name.clone(), + typ: check_type(env, &arg.typ).map_err(Error::msg)?, + }) +} + +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 typ = check_type(env, &f.typ).map_err(Error::msg)?; + let field = TypeField { + label: f.label.clone().into(), + typ: typ, + }; + res.push(field); + } + Ok(res) +} + +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)?; + if !env.pre && env.idl_env.as_func(&t).is_err() { + return Err(Error::msg(format!( + "method {} is a non-function type", + meth.id + ))); + } + res.push(Binding { + id: meth.id.to_owned(), + typ: t, + }); + } + Ok(res) +} + fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { match dec { Dec::TypD(binding) => { let t = check_type(env, &binding.typ)?; - env.insert_binding(binding.clone(), t); + env.insert_binding(Binding { + id: binding.id.to_owned(), + typ: t, + }); } Dec::ImportType(_) | Dec::ImportServ(_) => (), } @@ -193,8 +288,8 @@ 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 { - let is_duplicate = env.insert(id.to_string(), TypeInner::Unknown.into()); - if is_duplicate { + 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}"))); } } @@ -207,7 +302,7 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { Ok(()) } -fn check_actor(env: &Env, actor: &Option) -> Result> { +fn check_actor(env: &Env, actor: &Option) -> Result> { match actor { None => Ok(None), Some(typ) => { @@ -217,14 +312,14 @@ fn check_actor(env: &Env, actor: &Option) -> Result Result> { +pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result> { let mut env = Env { te, idl_env: &mut IDLEnv::new(), pre: false, }; check_decs(&mut env, &prog.decs)?; - check_actor(&env, &prog.actor).map(|t| t.map(|t| t.0)) + check_actor(&env, &prog.actor) } /// Type check init args extracted from canister metadata candid:args. /// Need to provide `main_env`, because init args may refer to variables from the main did file. @@ -299,43 +394,46 @@ pub fn check_init_args( env.te.merge(main_env)?; let mut args = Vec::new(); for arg in prog.args.iter() { - args.push(check_arg(&env, arg)?); + args.push(check_arg(&env, arg).and_then(|t| { + Ok(ArgType { + name: t.name, + typ: ast_to_type(&env.te, &t.typ)?, + }) + })?); } Ok(args) } fn merge_actor( env: &Env, - actor: &Option<(Type, IDLType)>, - imported: &Option<(Type, IDLType)>, + actor: &Option, + imported: &Option, file: &str, -) -> Result> { +) -> Result> { match imported { None => Err(Error::msg(format!( "Imported service file {file:?} has no main service" ))), - Some((t, idl_type)) => { - let t = env.te.trace_type(t)?; + Some(idl_type) => { + let t = env.idl_env.trace_type(idl_type).map_err(Error::msg)?; let idl_meths = env.idl_env.as_service(idl_type).map_err(Error::msg)?; - match t.as_ref() { - TypeInner::Class(_, _) => Err(Error::msg(format!( + match &t { + IDLType::ClassT(_, _) => Err(Error::msg(format!( "Imported service file {file:?} has a service constructor" ))), - TypeInner::Service(meths) => match actor { - None => Ok(Some((t, idl_type.clone()))), - Some((t, idl_type)) => { - let t = env.te.trace_type(t)?; - let serv = env.te.as_service(&t)?; - let idl_serv = env.idl_env.as_service(idl_type).map_err(Error::msg)?; + IDLType::ServT(meths) => match &actor { + None => Ok(Some(t.clone())), + Some(idl_type) => { + let t = env.idl_env.trace_type(idl_type).map_err(Error::msg)?; + let serv = env.idl_env.as_service(&t).map_err(Error::msg)?; let mut ms: Vec<_> = serv.iter().chain(meths.iter()).cloned().collect(); - ms.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - check_unique(ms.iter().map(|m| &m.0)).map_err(|e| { + ms.sort_unstable_by(|a, b| a.id.partial_cmp(&b.id).unwrap()); + check_unique(ms.iter().map(|m| &m.id)).map_err(|e| { Error::msg(format!("Duplicate imported method name: {e}")) })?; - let res: Type = TypeInner::Service(ms).into(); - let res_idl_meths = - idl_meths.iter().chain(idl_serv.iter()).cloned().collect(); - Ok(Some((res, IDLType::ServT(res_idl_meths)))) + let res = + IDLType::ServT(idl_meths.iter().chain(meths.iter()).cloned().collect()); + Ok(Some(res)) } }, _ => unreachable!(), @@ -344,7 +442,7 @@ fn merge_actor( } } -fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option)> { +fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option)> { let base = if file.is_absolute() { file.parent().unwrap().to_path_buf() } else { @@ -384,7 +482,7 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option< pre: false, }; - let mut actor: Option<(Type, IDLType)> = None; + let mut actor: Option = None; for (include_serv, path, name) in imports.iter() { let code = std::fs::read_to_string(path)?; let code = parse_idl_prog(&code)?; @@ -401,15 +499,15 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option< if actor.is_some() { res = merge_actor(&env, &res, &actor, "")?; } - idl_env.set_actor(res.clone().map(|t| t.1)); + idl_env.set_actor(res.clone()); - Ok((te, idl_env, res.map(|t| t.0))) + Ok((te, idl_env, res)) } /// Type check did file including the imports. -pub fn check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { +pub fn check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { check_file_(file, false) } -pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { +pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { check_file_(file, true) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 0c232ccb4..59dbebc29 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,6 +1,11 @@ -use crate::{check_prog, pretty_check_file, pretty_parse_idl_prog, Error, Result}; +use crate::{ + check_prog, pretty_check_file, pretty_parse_idl_prog, typing::ast_to_type, Error, Result, +}; use candid::{ - types::{syntax::IDLEnv, Type, TypeInner}, + types::{ + syntax::{IDLEnv, IDLType}, + Type, TypeInner, + }, TypeEnv, }; use std::path::Path; @@ -11,7 +16,7 @@ pub enum CandidSource<'a> { } impl CandidSource<'_> { - pub fn load(&self) -> Result<(TypeEnv, IDLEnv, Option)> { + pub fn load(&self) -> Result<(TypeEnv, IDLEnv, Option)> { Ok(match self { CandidSource::File(path) => pretty_check_file(path)?, CandidSource::Text(str) => { @@ -28,9 +33,13 @@ impl CandidSource<'_> { /// Check compatibility of two service types pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { let (mut env, _, t1) = new.load()?; - let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; + let t1 = t1 + .ok_or_else(|| Error::msg("new interface has no main service type")) + .and_then(|t| ast_to_type(&env, &t))?; let (env2, _, t2) = old.load()?; - let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; + let t2 = t2 + .ok_or_else(|| Error::msg("old interface has no main service type")) + .and_then(|t| ast_to_type(&env, &t))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); candid::types::subtype::subtype(&mut gamma, &env, &t1, &t2)?; @@ -40,9 +49,13 @@ pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { /// Check structural equality of two service types pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { let (mut env, _, t1) = left.load()?; - let t1 = t1.ok_or_else(|| Error::msg("left interface has no main service type"))?; + let t1 = t1 + .ok_or_else(|| Error::msg("left interface has no main service type")) + .and_then(|t| ast_to_type(&env, &t))?; let (env2, _, t2) = right.load()?; - let t2 = t2.ok_or_else(|| Error::msg("right interface has no main service type"))?; + let t2 = t2 + .ok_or_else(|| Error::msg("right interface has no main service type")) + .and_then(|t| ast_to_type(&env, &t))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); candid::types::subtype::equal(&mut gamma, &env, &t1, &t2)?; @@ -54,7 +67,9 @@ pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { /// For now, the comments from the original did file is omitted. pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, Type))> { let (env, _, serv) = candid.load()?; - let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; + let serv = serv + .ok_or_else(|| Error::msg("the Candid interface has no main service type")) + .and_then(|t| ast_to_type(&env, &t))?; let serv = env.trace_type(&serv)?; Ok(match serv.as_ref() { TypeInner::Class(args, ty) => ( @@ -65,22 +80,15 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, _ => unreachable!(), }) } -pub fn get_metadata(env: &TypeEnv, serv: &Option) -> Option { - let serv = serv.clone()?; - let serv = env.trace_type(&serv).ok()?; - let serv = match serv.as_ref() { - TypeInner::Class(_, ty) => ty.clone(), - TypeInner::Service(_) => serv, - _ => unreachable!(), - }; - let def_list = crate::bindings::analysis::chase_actor(env, &serv).ok()?; - let mut filtered = TypeEnv::new(); +pub fn get_metadata(env: &IDLEnv) -> Option { + let def_list = crate::bindings::analysis::chase_actor(env).ok()?; + let mut filtered = IDLEnv::new(); for d in def_list { - if let Some(t) = env.0.get(d) { - filtered.0.insert(d.to_string(), t.clone()); + if let Ok(b) = env.find_binding(d) { + filtered.insert_binding(b.clone()); } } - Some(candid::pretty::candid::compile(&filtered, &Some(serv))) + Some(candid::pretty::candid::compile(&filtered)) } /// Merge canister metadata candid:args and candid:service into a service constructor. @@ -90,7 +98,9 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { use candid::types::TypeInner; let candid = CandidSource::Text(candid); let (env, mut idl_env, serv) = candid.load()?; - let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; + let serv = serv + .ok_or_else(|| Error::msg("the Candid interface has no main service type")) + .and_then(|t| ast_to_type(&env, &t))?; let serv = env.trace_type(&serv)?; match serv.as_ref() { TypeInner::Class(_, _) => Ok((env, serv)), diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 716c1e17d..0cd3ce956 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -40,10 +40,10 @@ fn compiler_test(resource: &str) { let candid_path = base_path.join(filename); match check_file(&candid_path) { - Ok((env, idl_env, actor)) => { + Ok((_, idl_env, _)) => { { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); - let content = compile(&env, &actor); + let content = compile(&idl_env); // Type check output let ast = parse_idl_prog(&content).unwrap(); check_prog(&mut TypeEnv::new(), &ast).unwrap(); @@ -87,20 +87,20 @@ fn compiler_test(resource: &str) { _ => (), } let mut output = mint.new_goldenfile(filename.with_extension("rs")).unwrap(); - let (content, unused) = rust::compile(&config, &env, &actor, external); + let (content, unused) = rust::compile(&config, &idl_env, external); assert!(unused.is_empty()); writeln!(output, "{content}").unwrap(); } { let mut output = mint.new_goldenfile(filename.with_extension("js")).unwrap(); - let content = javascript::compile(&env, &actor); + let content = javascript::compile(&idl_env); writeln!(output, "{content}").unwrap(); } { let mut output = mint .new_goldenfile(filename.with_extension("d.ts")) .unwrap(); - let content = typescript::compile(&env, &actor); + let content = typescript::compile(&idl_env); writeln!(output, "{content}").unwrap(); } } diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index aa6e0a281..6a70c8b46 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -1,6 +1,7 @@ use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, TypeEnv}; use candid::{decode_args, decode_one, Decode}; +use candid_parser::typing::ast_to_type; use candid_parser::{parse_idl_args, parse_idl_prog, typing::check_prog}; #[test] @@ -34,6 +35,7 @@ service : { let ast = parse_idl_prog(candid).unwrap(); let mut env = TypeEnv::new(); let actor = check_prog(&mut env, &ast).unwrap().unwrap(); + let actor = ast_to_type(&env, &actor).unwrap(); let method = env.get_method(&actor, "f").unwrap(); { let args = parse_idl_args("(42,42,42,42)").unwrap(); diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 800471ae5..89dc42da9 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -1,8 +1,11 @@ use anyhow::{bail, Result}; -use candid_parser::candid::types::{ - subtype, - syntax::{IDLType, IDLTypes}, - Type, +use candid_parser::{ + candid::types::{ + subtype, + syntax::{IDLType, IDLTypes}, + Type, + }, + types::syntax::IDLEnv, }; use candid_parser::{ configs::Configs, parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, @@ -130,34 +133,36 @@ impl TypeAnnotation { fn is_empty(&self) -> bool { self.tys.is_none() && self.method.is_none() } - fn get_types(&self, mode: Mode) -> candid_parser::Result<(TypeEnv, Vec)> { - let (env, actor) = if let Some(ref file) = self.defs { - let (env, _, actor) = pretty_check_file(file)?; - (env, actor) + fn get_types( + &self, + mode: Mode, + ) -> candid_parser::Result<(TypeEnv, IDLEnv, Vec, Vec)> { + let (env, idl_env, actor) = if let Some(ref file) = self.defs { + pretty_check_file(file)? } else { - (TypeEnv::new(), None) + (TypeEnv::new(), IDLEnv::new(), None) }; - match (&self.tys, &self.method) { - (None, None) => Err(Error::msg("no type annotations")), - (Some(tys), None) => { - let mut types = Vec::new(); - for ty in tys.args.iter() { - types.push(ast_to_type(&env, ty)?); - } - Ok((env, types)) - } + let idl_types = match (&self.tys, &self.method) { + (None, None) => return Err(Error::msg("no type annotations")), + (Some(tys), None) => tys.args.clone(), (None, Some(meth)) => { let actor = actor .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; - let func = env.get_method(&actor, meth)?; - let types = match mode { + let func = idl_env + .get_method(&actor, meth) + .map_err(|e| Error::msg(e))?; + match mode { Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), Mode::Decode => func.rets.clone(), - }; - Ok((env, types)) + } } _ => unreachable!(), + }; + let mut types = Vec::new(); + for ty in idl_types.iter() { + types.push(ast_to_type(&env, ty)?); } + Ok((env, idl_env, types, idl_types)) } } @@ -195,7 +200,9 @@ fn main() -> Result<()> { if let Some(previous) = previous { let (env2, _, opt_t2) = pretty_check_file(&previous)?; match (opt_t1, opt_t2) { - (Some(t1), Some(t2)) => { + (Some(idl_t1), Some(idl_t2)) => { + let t1 = ast_to_type(&env, &idl_t1)?; + let t2 = ast_to_type(&env2, &idl_t2)?; let mut gamma = HashSet::new(); let t2 = env.merge_type(env2, t2); if strict { @@ -224,20 +231,15 @@ fn main() -> Result<()> { input, target, config, - methods, + methods: _, } => { let configs = load_config(&config)?; - let (env, prog, mut actor) = pretty_check_file(&input)?; - if !methods.is_empty() { - actor = Some(candid_parser::bindings::analysis::project_methods( - &env, &actor, methods, - )?); - } + let (_, idl_env, _) = pretty_check_file(&input)?; let content = match target.as_str() { - "js" => candid_parser::bindings::javascript::compile(&env, &actor), - "ts" => candid_parser::bindings::typescript::compile(&env, &actor), - "did" => candid_parser::pretty::candid::compile(&env, &actor), - "mo" => candid_parser::bindings::motoko::compile(&prog), + "js" => candid_parser::bindings::javascript::compile(&idl_env), + "ts" => candid_parser::bindings::typescript::compile(&idl_env), + "did" => candid_parser::pretty::candid::compile(&idl_env), + "mo" => candid_parser::bindings::motoko::compile(&idl_env), "rs" => { use candid_parser::bindings::rust::{compile, Config, ExternalConfig}; let external = configs @@ -245,7 +247,7 @@ fn main() -> Result<()> { .map(|x| x.clone().try_into().unwrap()) .unwrap_or(ExternalConfig::default()); let config = Config::new(configs); - let (res, unused) = compile(&config, &env, &actor, external); + let (res, unused) = compile(&config, &idl_env, external); warn_unused(&unused); res } @@ -259,7 +261,7 @@ fn main() -> Result<()> { _ => unreachable!(), }; external.0.insert("target".to_string(), target.to_string()); - let (res, unused) = compile(&config, &env, &actor, external); + let (res, unused) = compile(&config, &idl_env, external); warn_unused(&unused); res } @@ -286,7 +288,7 @@ fn main() -> Result<()> { } Command::Assist { annotate } => { use candid_parser::assist::{input_args, Context}; - let (env, types) = annotate.get_types(Mode::Encode)?; + let (env, _, types, _) = annotate.get_types(Mode::Encode)?; let ctx = Context::new(env.clone()); let args = input_args(&ctx, &types)?; println!("{args}"); @@ -308,7 +310,7 @@ fn main() -> Result<()> { let bytes = if annotate.is_empty() { args.to_bytes()? } else { - let (env, types) = annotate.get_types(Mode::Encode)?; + let (env, _, types, _) = annotate.get_types(Mode::Encode)?; args.to_bytes_with_types(&env, &types)? }; let hex = match format.as_str() { @@ -349,7 +351,7 @@ fn main() -> Result<()> { let value = if annotate.is_empty() { IDLArgs::from_bytes(&bytes)? } else { - let (env, types) = annotate.get_types(Mode::Decode)?; + let (env, _, types, _) = annotate.get_types(Mode::Decode)?; IDLArgs::from_bytes_with_types(&bytes, &env, &types)? }; println!("{value}"); @@ -362,7 +364,7 @@ fn main() -> Result<()> { } => { use candid_parser::configs::{Scope, ScopePos}; use rand::Rng; - let (env, types) = if args.is_some() { + let (_, idl_env, _, idl_types) = if args.is_some() { annotate.get_types(Mode::Decode)? } else { annotate.get_types(Mode::Encode)? @@ -370,7 +372,7 @@ fn main() -> Result<()> { let config = load_config(&config)?; // TODO figure out how many bytes of entropy we need let seed: Vec = if let Some(ref args) = args { - let (env, types) = annotate.get_types(Mode::Encode)?; + let (env, _, types, _) = annotate.get_types(Mode::Encode)?; let bytes = args.to_bytes_with_types(&env, &types)?; bytes.into_iter().rev().cycle().take(2048).collect() } else { @@ -385,7 +387,7 @@ fn main() -> Result<()> { }); Scope { position, method } }); - let args = candid_parser::random::any(&seed, config, &env, &types, &scope)?; + let args = candid_parser::random::any(&seed, config, &idl_env, &idl_types, &scope)?; match lang.as_str() { "did" => println!("{args}"), "js" => println!( From 05511c74adf518eb6ec44541160f25c8512ace7d Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 13:54:14 +0200 Subject: [PATCH 08/34] fix: add unknown types --- rust/candid/src/pretty/candid.rs | 3 +- rust/candid/src/types/syntax.rs | 78 ++++++++++++------- rust/candid/src/types/type_env.rs | 26 ++++++- rust/candid_parser/src/bindings/javascript.rs | 4 +- rust/candid_parser/src/bindings/motoko.rs | 14 ++-- rust/candid_parser/src/bindings/rust.rs | 26 +++---- rust/candid_parser/src/bindings/typescript.rs | 8 +- rust/candid_parser/src/configs.rs | 1 + rust/candid_parser/src/typing.rs | 32 ++------ rust/candid_parser/src/utils.rs | 9 ++- rust/candid_parser/tests/assets/ok/class.d.ts | 2 +- rust/candid_parser/tests/assets/ok/class.did | 2 +- .../candid_parser/tests/assets/ok/cyclic.d.ts | 2 +- rust/candid_parser/tests/assets/ok/cyclic.did | 2 +- .../tests/assets/ok/fieldnat.d.ts | 2 +- .../tests/assets/ok/fieldnat.did | 2 +- .../tests/assets/ok/keyword.d.ts | 8 +- .../candid_parser/tests/assets/ok/keyword.did | 8 +- .../tests/assets/ok/management.d.ts | 52 ++++++------- .../tests/assets/ok/management.did | 40 +++++----- .../tests/assets/ok/recursion.d.ts | 8 +- .../tests/assets/ok/recursion.did | 8 +- .../tests/assets/ok/service.d.ts | 2 +- .../candid_parser/tests/assets/ok/service.did | 2 +- .../tests/assets/ok/undefine.fail | 2 +- 25 files changed, 188 insertions(+), 155 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 84e10030b..14ed8c235 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -125,6 +125,7 @@ pub fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } + UnknownT => unreachable!(), } } @@ -203,7 +204,7 @@ fn pp_service(serv: &[Binding]) -> RcDoc { } fn pp_defs(env: &IDLEnv) -> RcDoc { - lines(env.types_bindings.iter().map(|Binding { id, typ }| { + lines(env.get_bindings().iter().map(|(id, typ)| { kwd("type") .append(ident(id)) .append(kwd("=")) diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index a029830f1..5f7ca6a6b 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeSet, fmt}; +use std::{collections::BTreeMap, fmt}; use crate::types::{FuncMode, Label}; @@ -14,6 +14,7 @@ pub enum IDLType { ServT(Vec), ClassT(Vec, Box), PrincipalT, + UnknownT, } impl IDLType { @@ -147,37 +148,28 @@ pub struct IDLInitArgs { #[derive(Debug, Default)] pub struct IDLEnv { - pub types_bindings: Vec, - types_bindings_ids: BTreeSet, + types: BTreeMap>, + types_bindings_ids: Vec, pub actor: Option, } impl From<&IDLProg> for IDLEnv { fn from(prog: &IDLProg) -> Self { - let mut types_bindings_ids = BTreeSet::new(); - let mut types_bindings = Vec::new(); + let mut env = Self::new(); for dec in prog.decs.iter() { if let Dec::TypD(binding) = dec { - let is_duplicate = types_bindings_ids.insert(binding.id.clone()); - if !is_duplicate { - types_bindings.push(binding.clone()); - } + env.insert_binding(binding.clone()); } } - let mut env = Self { - types_bindings, - types_bindings_ids, - actor: None, - }; env.set_actor(prog.actor.clone()); env } } -impl From> for IDLEnv { - fn from(bindings: Vec<&Binding>) -> Self { +impl From> for IDLEnv { + fn from(bindings: Vec) -> Self { let mut env = Self::default(); for binding in bindings { env.insert_binding(binding.clone()); @@ -192,13 +184,22 @@ impl IDLEnv { } pub fn insert_binding(&mut self, binding: Binding) { - let is_new = self.types_bindings_ids.insert(binding.id.clone()); - if is_new { - self.types_bindings.push(binding); + let duplicate = self + .types + .insert(binding.id.clone(), Some(binding.typ.clone())); + if duplicate.is_none() { + self.types_bindings_ids.push(binding.id.clone()); + } + } + + pub fn insert_unknown(&mut self, id: &str) { + let duplicate = self.types.insert(id.to_string(), None); + if duplicate.is_none() { + self.types_bindings_ids.push(id.to_string()); } } - pub fn bindings_ids(&self) -> Vec<&str> { + pub fn types_ids(&self) -> Vec<&str> { self.types_bindings_ids .iter() .map(|id| id.as_str()) @@ -209,15 +210,40 @@ impl IDLEnv { self.actor = actor; } - pub fn find_binding(&self, id: &str) -> Result<&Binding, String> { - self.types_bindings - .iter() - .find(|b| b.id == id) - .ok_or(format!("Unbound type identifier: {id}")) + pub fn find_binding<'a>(&'a self, id: &'a str) -> Result<(&'a str, &'a IDLType), String> { + self.find_type(id).map(|t| (id, t)) } pub fn find_type(&self, id: &str) -> Result<&IDLType, String> { - self.find_binding(id).map(|b| &b.typ) + self.types + .get(id) + .ok_or(format!("Type identifier not found: {id}")) + .and_then(|t| { + t.as_ref() + .ok_or(format!("Type identifier is unknown: {id}")) + }) + } + + pub fn assert_has_type(&self, id: &str) -> Result<(), String> { + if self.types.contains_key(id) { + Ok(()) + } else { + Err(format!("Type identifier not found: {id}")) + } + } + + pub fn get_types(&self) -> Vec<&IDLType> { + self.types_bindings_ids + .iter() + .map(|id| self.find_type(id).unwrap()) + .collect() + } + + pub fn get_bindings(&self) -> Vec<(&str, &IDLType)> { + self.types_bindings_ids + .iter() + .map(|id| self.find_binding(id).unwrap()) + .collect() } pub fn rec_find_type(&self, name: &str) -> Result<&IDLType, String> { diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 0723f2769..78590b436 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,6 +1,6 @@ use crate::types::{Function, Type, TypeInner}; use crate::{Error, Result}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Debug, Clone, Default)] pub struct TypeEnv(pub BTreeMap); @@ -130,6 +130,30 @@ impl TypeEnv { } Ok(()) } + + fn has_cycle<'a>(&'a self, seen: &mut BTreeSet<&'a str>, t: &'a Type) -> Result { + match t.as_ref() { + TypeInner::Var(id) => { + if seen.insert(id) { + let ty = self.find_type(id)?; + self.has_cycle(seen, ty) + } else { + Ok(true) + } + } + _ => Ok(false), + } + } + + pub fn check_cycle(&self) -> Result<()> { + for (id, ty) in self.0.iter() { + let mut seen = BTreeSet::new(); + if self.has_cycle(&mut seen, ty)? { + return Err(Error::msg(format!("{id} has cyclic type definition"))); + } + } + Ok(()) + } } impl std::fmt::Display for TypeEnv { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 1bbb45c75..2e5052d0d 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -136,7 +136,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), FuncT(ref func) => str("IDL.Func").append(pp_function(func)), ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), - ClassT(_, _) => unreachable!(), + ClassT(_, _) | UnknownT => unreachable!(), } } @@ -239,7 +239,7 @@ fn pp_actor<'a>(ty: &'a IDLType, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { pub fn compile(env: &IDLEnv) -> String { match &env.actor { None => { - let def_list: Vec<_> = env.bindings_ids(); + let def_list: Vec<_> = env.types_ids(); let recs = infer_rec(env, &def_list).unwrap(); let doc = pp_defs(env, &def_list, &recs); 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 4219f2579..0e0889c70 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -147,6 +147,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } + UnknownT => unreachable!(), } } @@ -251,12 +252,12 @@ fn pp_service(serv: &[Binding]) -> RcDoc { kwd("actor").append(enclose_space("{", doc, "}")) } -fn pp_defs(bindings: &[Binding]) -> RcDoc { - lines(bindings.iter().map(|binding| { +fn pp_defs<'a>(bindings: &[(&'a str, &'a IDLType)]) -> RcDoc<'a> { + lines(bindings.iter().map(|(id, typ)| { kwd("public type") - .append(escape(&binding.id, false)) + .append(escape(&id, false)) .append(" = ") - .append(pp_ty(&binding.typ)) + .append(pp_ty(&typ)) .append(";") })) } @@ -273,10 +274,11 @@ pub fn compile(env: &IDLEnv) -> String { let header = r#"// This is a generated Motoko binding. // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. "#; + let bindings = env.get_bindings(); let doc = match &env.actor { - None => pp_defs(&env.types_bindings), + None => pp_defs(&bindings), Some(actor) => { - let defs = pp_defs(&env.types_bindings); + let defs = pp_defs(&bindings); let actor = kwd("public type Self =").append(pp_actor(actor)); defs.append(actor) } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 344f7e77b..c40b3289f 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -174,9 +174,13 @@ impl<'a> State<'a> { let env = IDLEnv::from( self.state .env - .types_bindings - .iter() - .filter(|Binding { id, typ: _ }| def_list.contains(&id.as_str())) + .get_bindings() + .into_iter() + .filter(|(id, _)| def_list.contains(id)) + .map(|(id, typ)| Binding { + id: id.to_string(), + typ: typ.clone(), + }) .collect::>(), ); let src = candid::pretty::candid::pp_init_args( @@ -283,7 +287,7 @@ fn test_{test_name}() {{ } FuncT(_) => unreachable!(), // not possible after rewriting ServT(_) => unreachable!(), // not possible after rewriting - ClassT(_, _) => unreachable!(), + ClassT(_, _) | UnknownT => unreachable!(), } }; self.state.pop_state(old, elem); @@ -705,7 +709,7 @@ pub fn emit_bindgen(tree: &Config, env: &IDLEnv) -> (Output, Vec) { let def_list = if env.actor.is_some() { chase_actor(&env).unwrap() } else { - env.bindings_ids() + env.types_ids() }; let recs = infer_rec(&env, &def_list).unwrap(); let mut state = State { @@ -1066,16 +1070,12 @@ impl NominalState<'_> { fn nominalize_all(&mut self) -> IDLEnv { let mut res = IDLEnv::new(); - for binding in self.state.env.types_bindings.iter() { - let elem = StateElem::Label(binding.id.as_str()); + for (id, typ) in self.state.env.get_bindings() { + let elem = StateElem::Label(id); let old = self.state.push_state(&elem); - let ty = self.nominalize( - &mut res, - &mut vec![TypePath::Id(binding.id.clone())], - &binding.typ, - ); + let ty = self.nominalize(&mut res, &mut vec![TypePath::Id(id.to_string())], typ); res.insert_binding(Binding { - id: binding.id.clone(), + id: id.to_string(), typ: ty, }); 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 641a32166..0a6b19d73 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -104,7 +104,7 @@ fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { } FuncT(_) => str("[Principal, string]"), ServT(_) => str("Principal"), - ClassT(_, _) => unreachable!(), + ClassT(_, _) | UnknownT => unreachable!(), } } @@ -200,11 +200,7 @@ pub fn compile(env: &IDLEnv) -> String { import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; "#; - let def_list: Vec<_> = env - .types_bindings - .iter() - .map(|Binding { id, typ: _ }| id.as_ref()) - .collect(); + let def_list = env.types_ids(); let defs = pp_defs(env, &def_list); let actor = match &env.actor { None => RcDoc::nil(), diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index c25903566..ec1361991 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -408,6 +408,7 @@ fn path_name(t: &IDLType) -> String { IDLType::FuncT(_) => "func", IDLType::ServT(_) => "service", IDLType::ClassT(..) => "func:init", + IDLType::UnknownT => unreachable!(), } .to_string() } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index f6f9b0a12..3fef3b17f 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -7,7 +7,7 @@ use candid::types::{ ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; pub struct Env<'a> { @@ -109,6 +109,7 @@ pub fn map_type(env: &Env, t: &IDLType) -> Result { Ok(TypeInner::Service(ms).into()) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + IDLType::UnknownT => Err(Error::msg("unknown type")), } } @@ -153,7 +154,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { match t { IDLType::PrimT(prim) => Ok(IDLType::PrimT(prim.clone())), IDLType::VarT(id) => { - env.idl_env.find_type(id).map_err(Error::msg)?; + env.idl_env.assert_has_type(id).map_err(Error::msg)?; Ok(IDLType::VarT(id.to_string())) } IDLType::OptT(t) => { @@ -203,6 +204,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { Ok(IDLType::ServT(ms)) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + IDLType::UnknownT => Err(Error::msg("unknown type")), } } @@ -262,29 +264,6 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { Ok(()) } -fn check_cycle(env: &TypeEnv) -> Result<()> { - fn has_cycle<'a>(seen: &mut BTreeSet<&'a str>, env: &'a TypeEnv, t: &'a Type) -> Result { - match t.as_ref() { - TypeInner::Var(id) => { - if seen.insert(id) { - let ty = env.find_type(id)?; - has_cycle(seen, env, ty) - } else { - Ok(true) - } - } - _ => Ok(false), - } - } - for (id, ty) in env.0.iter() { - let mut seen = BTreeSet::new(); - if has_cycle(&mut seen, env, ty)? { - return Err(Error::msg(format!("{id} has cyclic type definition"))); - } - } - Ok(()) -} - fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, typ: _ }) = dec { @@ -292,11 +271,12 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { if duplicate.is_some() { return Err(Error::msg(format!("duplicate binding for {id}"))); } + env.idl_env.insert_unknown(id); } } env.pre = true; check_defs(env, decs)?; - check_cycle(env.te)?; + env.te.check_cycle()?; env.pre = false; check_defs(env, decs)?; Ok(()) diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 59dbebc29..381eb406c 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -3,7 +3,7 @@ use crate::{ }; use candid::{ types::{ - syntax::{IDLEnv, IDLType}, + syntax::{Binding, IDLEnv, IDLType}, Type, TypeInner, }, TypeEnv, @@ -84,8 +84,11 @@ pub fn get_metadata(env: &IDLEnv) -> Option { let def_list = crate::bindings::analysis::chase_actor(env).ok()?; let mut filtered = IDLEnv::new(); for d in def_list { - if let Ok(b) = env.find_binding(d) { - filtered.insert_binding(b.clone()); + if let Ok((id, typ)) = env.find_binding(d) { + filtered.insert_binding(Binding { + id: id.to_string(), + typ: typ.clone(), + }); } } Some(candid::pretty::candid::compile(&filtered)) diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index 412ee8514..c72dd1cb6 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -2,8 +2,8 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export type List = [] | [[bigint, List]]; export interface Profile { 'age' : number, 'name' : string } +export type List = [] | [[bigint, List]]; export interface _SERVICE { 'get' : ActorMethod<[], List>, 'set' : ActorMethod<[List], List>, diff --git a/rust/candid_parser/tests/assets/ok/class.did b/rust/candid_parser/tests/assets/ok/class.did index 4850a508e..645c002b0 100644 --- a/rust/candid_parser/tests/assets/ok/class.did +++ b/rust/candid_parser/tests/assets/ok/class.did @@ -1,5 +1,5 @@ -type List = opt record { int; List }; type Profile = record { age : nat8; name : text }; +type List = opt record { int; List }; service : (int, l : List, Profile) -> { get : () -> (List); set : (List) -> (List); diff --git a/rust/candid_parser/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts index d75b132a3..42390de9d 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.d.ts +++ b/rust/candid_parser/tests/assets/ok/cyclic.d.ts @@ -3,8 +3,8 @@ import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; export type A = [] | [B]; -export type B = [] | [C]; export type C = A; +export type B = [] | [C]; export type X = Y; export type Y = Z; export type Z = A; diff --git a/rust/candid_parser/tests/assets/ok/cyclic.did b/rust/candid_parser/tests/assets/ok/cyclic.did index eebf3369f..fd0c4b40e 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.did +++ b/rust/candid_parser/tests/assets/ok/cyclic.did @@ -1,6 +1,6 @@ type A = opt B; -type B = opt C; type C = A; +type B = opt C; type X = Y; type Y = Z; type Z = A; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts index 53e68afe1..5853eff15 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts +++ b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts @@ -2,8 +2,8 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export interface non_tuple { _1_ : string, _2_ : string } export type tuple = [string, string]; +export interface non_tuple { _1_ : string, _2_ : string } export interface _SERVICE { 'bab' : ActorMethod<[bigint, bigint], undefined>, 'bar' : ActorMethod<[{ '2' : bigint }], { 'e20' : null } | { 'e30' : null }>, diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.did b/rust/candid_parser/tests/assets/ok/fieldnat.did index 921423bb4..1f5e2a585 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.did +++ b/rust/candid_parser/tests/assets/ok/fieldnat.did @@ -1,5 +1,5 @@ -type non_tuple = record { 1 : text; 2 : text }; type tuple = record { text; text }; +type non_tuple = record { 1 : text; 2 : text }; service : { bab : (two : int, nat) -> (); bar : (record { "2" : int }) -> (variant { e20; e30 }); diff --git a/rust/candid_parser/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 22d1c24a6..d80db1608 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.d.ts +++ b/rust/candid_parser/tests/assets/ok/keyword.d.ts @@ -2,16 +2,16 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; +export type o = [] | [o]; +export interface node { 'head' : bigint, 'tail' : list } +export type list = [] | [node]; export type if_ = { 'branch' : { 'val' : bigint, 'left' : if_, 'right' : if_ } } | { 'leaf' : bigint }; -export type list = [] | [node]; -export interface node { 'head' : bigint, 'tail' : list } -export type o = [] | [o]; export interface return_ { 'f' : t, 'g' : ActorMethod<[list], [if_, stream]> } -export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; export type t = ActorMethod<[Principal], undefined>; +export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; export interface _SERVICE { 'Oneway' : ActorMethod<[], undefined>, 'f_' : ActorMethod<[o], o>, diff --git a/rust/candid_parser/tests/assets/ok/keyword.did b/rust/candid_parser/tests/assets/ok/keyword.did index 43965aefa..9b1d2cec1 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.did +++ b/rust/candid_parser/tests/assets/ok/keyword.did @@ -1,13 +1,13 @@ +type o = opt o; +type node = record { head : nat; tail : list }; +type list = opt node; type if = variant { branch : record { val : int; left : if; right : if }; leaf : int; }; -type list = opt node; -type node = record { head : nat; tail : list }; -type o = opt o; type return = service { f : t; g : (list) -> (if, stream) }; -type stream = opt record { head : nat; next : func () -> (stream) query }; type t = func (server : return) -> (); +type stream = opt record { head : nat; next : func () -> (stream) query }; service : { Oneway : () -> () oneway; f_ : (o) -> (o); diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index ca60e354d..66e4babca 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -2,11 +2,9 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export type bitcoin_address = string; -export type bitcoin_network = { 'mainnet' : null } | - { 'testnet' : null }; -export type block_hash = Uint8Array | number[]; export type canister_id = Principal; +export type user_id = Principal; +export type wasm_module = Uint8Array | number[]; export interface canister_settings { 'freezing_threshold' : [] | [bigint], 'controllers' : [] | [Array], @@ -19,14 +17,23 @@ export interface definite_canister_settings { 'memory_allocation' : bigint, 'compute_allocation' : bigint, } -export type ecdsa_curve = { 'secp256k1' : null }; -export interface get_balance_request { - 'network' : bitcoin_network, - 'address' : bitcoin_address, - 'min_confirmations' : [] | [number], +export interface http_header { 'value' : string, 'name' : string } +export interface http_response { + 'status' : bigint, + 'body' : Uint8Array | number[], + 'headers' : Array, } -export interface get_current_fee_percentiles_request { - 'network' : bitcoin_network, +export type ecdsa_curve = { 'secp256k1' : null }; +export type satoshi = bigint; +export type bitcoin_network = { 'mainnet' : null } | + { 'testnet' : null }; +export type bitcoin_address = string; +export type block_hash = Uint8Array | number[]; +export interface outpoint { 'txid' : Uint8Array | number[], 'vout' : number } +export interface utxo { + 'height' : number, + 'value' : satoshi, + 'outpoint' : outpoint, } export interface get_utxos_request { 'network' : bitcoin_network, @@ -36,32 +43,25 @@ export interface get_utxos_request { ], 'address' : bitcoin_address, } +export interface get_current_fee_percentiles_request { + 'network' : bitcoin_network, +} export interface get_utxos_response { 'next_page' : [] | [Uint8Array | number[]], 'tip_height' : number, 'tip_block_hash' : block_hash, 'utxos' : Array, } -export interface http_header { 'value' : string, 'name' : string } -export interface http_response { - 'status' : bigint, - 'body' : Uint8Array | number[], - 'headers' : Array, +export interface get_balance_request { + 'network' : bitcoin_network, + 'address' : bitcoin_address, + 'min_confirmations' : [] | [number], } -export type millisatoshi_per_byte = bigint; -export interface outpoint { 'txid' : Uint8Array | number[], 'vout' : number } -export type satoshi = bigint; export interface send_transaction_request { 'transaction' : Uint8Array | number[], 'network' : bitcoin_network, } -export type user_id = Principal; -export interface utxo { - 'height' : number, - 'value' : satoshi, - 'outpoint' : outpoint, -} -export type wasm_module = Uint8Array | number[]; +export type millisatoshi_per_byte = bigint; export interface _SERVICE { 'bitcoin_get_balance' : ActorMethod<[get_balance_request], satoshi>, 'bitcoin_get_current_fee_percentiles' : ActorMethod< diff --git a/rust/candid_parser/tests/assets/ok/management.did b/rust/candid_parser/tests/assets/ok/management.did index 4af885072..db9ccda59 100644 --- a/rust/candid_parser/tests/assets/ok/management.did +++ b/rust/candid_parser/tests/assets/ok/management.did @@ -1,7 +1,6 @@ -type bitcoin_address = text; -type bitcoin_network = variant { mainnet; testnet }; -type block_hash = blob; type canister_id = principal; +type user_id = principal; +type wasm_module = blob; type canister_settings = record { freezing_threshold : opt nat; controllers : opt vec principal; @@ -14,40 +13,41 @@ type definite_canister_settings = record { memory_allocation : nat; compute_allocation : nat; }; -type ecdsa_curve = variant { secp256k1 }; -type get_balance_request = record { - network : bitcoin_network; - address : bitcoin_address; - min_confirmations : opt nat32; +type http_header = record { value : text; name : text }; +type http_response = record { + status : nat; + body : blob; + headers : vec http_header; }; -type get_current_fee_percentiles_request = record { network : bitcoin_network }; +type ecdsa_curve = variant { secp256k1 }; +type satoshi = nat64; +type bitcoin_network = variant { mainnet; testnet }; +type bitcoin_address = text; +type block_hash = blob; +type outpoint = record { txid : blob; vout : nat32 }; +type utxo = record { height : nat32; value : satoshi; outpoint : outpoint }; type get_utxos_request = record { network : bitcoin_network; filter : opt variant { page : blob; min_confirmations : nat32 }; address : bitcoin_address; }; +type get_current_fee_percentiles_request = record { network : bitcoin_network }; type get_utxos_response = record { next_page : opt blob; tip_height : nat32; tip_block_hash : block_hash; utxos : vec utxo; }; -type http_header = record { value : text; name : text }; -type http_response = record { - status : nat; - body : blob; - headers : vec http_header; +type get_balance_request = record { + network : bitcoin_network; + address : bitcoin_address; + min_confirmations : opt nat32; }; -type millisatoshi_per_byte = nat64; -type outpoint = record { txid : blob; vout : nat32 }; -type satoshi = nat64; type send_transaction_request = record { transaction : blob; network : bitcoin_network; }; -type user_id = principal; -type utxo = record { height : nat32; value : satoshi; outpoint : outpoint }; -type wasm_module = blob; +type millisatoshi_per_byte = nat64; service : { bitcoin_get_balance : (get_balance_request) -> (satoshi); bitcoin_get_current_fee_percentiles : ( diff --git a/rust/candid_parser/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 062ef95a3..7fb159c38 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursion.d.ts @@ -4,15 +4,15 @@ import type { IDL } from '@dfinity/candid'; export type A = B; export type B = [] | [A]; -export type list = [] | [node]; export interface node { 'head' : bigint, 'tail' : list } -export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } -export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; -export type t = ActorMethod<[Principal], undefined>; +export type list = [] | [node]; export type tree = { 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } } | { 'leaf' : bigint }; +export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } +export type t = ActorMethod<[Principal], undefined>; +export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; export interface _SERVICE extends s {} export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/recursion.did b/rust/candid_parser/tests/assets/ok/recursion.did index bc4278a62..623a3296a 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.did +++ b/rust/candid_parser/tests/assets/ok/recursion.did @@ -1,12 +1,12 @@ type A = B; type B = opt A; -type list = opt node; type node = record { head : nat; tail : list }; -type s = service { f : t; g : (list) -> (B, tree, stream) }; -type stream = opt record { head : nat; next : func () -> (stream) query }; -type t = func (server : s) -> (); +type list = opt node; type tree = variant { branch : record { val : int; left : tree; right : tree }; leaf : int; }; +type s = service { f : t; g : (list) -> (B, tree, stream) }; +type t = func (server : s) -> (); +type stream = opt record { head : nat; next : func () -> (stream) query }; service : s diff --git a/rust/candid_parser/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts index 57c3c871d..097ccc9cc 100644 --- a/rust/candid_parser/tests/assets/ok/service.d.ts +++ b/rust/candid_parser/tests/assets/ok/service.d.ts @@ -2,9 +2,9 @@ import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; -export type Func = ActorMethod<[], Principal>; export interface Service { 'f' : Func } export type Service2 = Service; +export type Func = ActorMethod<[], Principal>; export interface _SERVICE { 'asArray' : ActorMethod<[], [Array, Array<[Principal, string]>]>, 'asPrincipal' : ActorMethod<[], [Principal, [Principal, string]]>, diff --git a/rust/candid_parser/tests/assets/ok/service.did b/rust/candid_parser/tests/assets/ok/service.did index 672d699b5..7dc21afac 100644 --- a/rust/candid_parser/tests/assets/ok/service.did +++ b/rust/candid_parser/tests/assets/ok/service.did @@ -1,6 +1,6 @@ -type Func = func () -> (Service); type Service = service { f : Func }; type Service2 = Service; +type Func = func () -> (Service); service : { asArray : () -> (vec Service2, vec Func) query; asPrincipal : () -> (Service2, Func); diff --git a/rust/candid_parser/tests/assets/ok/undefine.fail b/rust/candid_parser/tests/assets/ok/undefine.fail index 5fc7eee7e..b59b1f2be 100644 --- a/rust/candid_parser/tests/assets/ok/undefine.fail +++ b/rust/candid_parser/tests/assets/ok/undefine.fail @@ -1 +1 @@ -Unbound type identifier a +Type identifier not found: a From e44d50aef8ed4112880f5b950350e4051e95ab90 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 14:13:32 +0200 Subject: [PATCH 09/34] fix: trace_type --- rust/candid/src/types/syntax.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 5f7ca6a6b..c2241ad79 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -257,7 +257,6 @@ impl IDLEnv { pub fn trace_type(&self, t: &IDLType) -> Result { match t { IDLType::VarT(id) => self.trace_type(self.find_type(id)?), - IDLType::ClassT(_, t) => self.trace_type(t), _ => Ok(t.clone()), } } From b50529bc74c43466c5724c36281d3c9c0083798e Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 15:48:24 +0200 Subject: [PATCH 10/34] refactor: use `Error::msg` --- rust/candid_parser/src/bindings/analysis.rs | 36 +++++---------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index 4962815fe..5e468005e 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -11,9 +11,7 @@ pub fn project_methods( actor: &Option, mut methods: Vec, ) -> Result { - let actor = actor - .as_ref() - .ok_or_else(|| Error::Custom(anyhow::anyhow!("no actor")))?; + let actor = actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; let service = env.as_service(actor)?; let filtered = service .iter() @@ -28,10 +26,7 @@ pub fn project_methods( .cloned() .collect(); if !methods.is_empty() { - return Err(Error::Custom(anyhow::anyhow!( - "methods not found: {:?}", - methods - ))); + return Err(Error::msg(format!("methods not found: {:?}", methods))); } Ok(TypeInner::Service(filtered).into()) } @@ -47,9 +42,7 @@ pub fn chase_type<'a>( match t { VarT(id) => { if seen.insert(id) { - let t = env - .find_type(id) - .map_err(|e| Error::Custom(anyhow::anyhow!(e)))?; + let t = env.find_type(id).map_err(Error::msg)?; chase_type(seen, res, env, t)?; res.push(id); } @@ -87,23 +80,15 @@ pub fn chase_type<'a>( pub fn chase_actor<'a>(env: &'a IDLEnv) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); - let actor = env - .actor - .as_ref() - .ok_or_else(|| Error::Custom(anyhow::anyhow!("no actor")))?; + let actor = env.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; chase_type(&mut seen, &mut res, env, actor)?; Ok(res) } /// Given an actor, return a map from variable names to the (methods, arg) that use them. pub fn chase_def_use<'a>(env: &'a IDLEnv) -> Result>> { let mut res = BTreeMap::new(); - let actor = env - .actor - .as_ref() - .ok_or_else(|| Error::Custom(anyhow::anyhow!("no actor")))?; - let actor = env - .trace_type(&actor) - .map_err(|e| Error::Custom(anyhow::anyhow!(e)))?; + let actor = env.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; + let actor = env.trace_type(&actor).map_err(Error::msg)?; if let IDLType::ClassT(args, _) = &actor { for (i, arg) in args.iter().enumerate() { let mut used = Vec::new(); @@ -115,13 +100,8 @@ pub fn chase_def_use<'a>(env: &'a IDLEnv) -> Result } } } - for binding in env - .as_service(&actor) - .map_err(|e| Error::Custom(anyhow::anyhow!(e)))? - { - let func = env - .as_func(&binding.typ) - .map_err(|e| Error::Custom(anyhow::anyhow!(e)))?; + for binding in env.as_service(&actor).map_err(Error::msg)? { + let func = env.as_func(&binding.typ).map_err(Error::msg)?; for (i, arg) in func.args.iter().enumerate() { let mut used = Vec::new(); chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; From f39988b8a79ec8ba04315399269988edcb91ec22 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 16:26:09 +0200 Subject: [PATCH 11/34] fix: get_metadata actor fix --- rust/candid/src/types/type_env.rs | 119 ++++++++++++++- rust/candid_parser/src/bindings/analysis.rs | 3 +- rust/candid_parser/src/bindings/javascript.rs | 2 +- rust/candid_parser/src/bindings/rust.rs | 6 +- rust/candid_parser/src/typing.rs | 142 +----------------- rust/candid_parser/src/utils.rs | 10 +- 6 files changed, 140 insertions(+), 142 deletions(-) diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 78590b436..4fca9f963 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,4 +1,5 @@ -use crate::types::{Function, Type, TypeInner}; +use crate::types::syntax::{Binding, IDLArgType, IDLType, PrimType, TypeField}; +use crate::types::{ArgType, Field, FuncMode, Function, Type, TypeInner}; use crate::{Error, Result}; use std::collections::{BTreeMap, BTreeSet}; @@ -154,7 +155,100 @@ impl TypeEnv { } Ok(()) } + + fn map_arg(&self, arg: &IDLArgType) -> Result { + Ok(ArgType { + name: arg.name.clone(), + typ: self.map_type(&arg.typ)?, + }) + } + + fn map_fields(&self, fs: &[TypeField]) -> Result> { + // field label duplication is checked in the parser + let mut res = Vec::new(); + for f in fs.iter() { + let ty = self.map_type(&f.typ)?; + let field = Field { + id: f.label.clone().into(), + ty, + }; + res.push(field); + } + Ok(res) + } + + fn map_meths(&self, ms: &[Binding]) -> Result> { + // binding duplication is checked in the parser + let mut res = Vec::new(); + for meth in ms.iter() { + let t = self.map_type(&meth.typ)?; + if self.as_func(&t).is_err() { + return Err(Error::msg(format!( + "method {} is a non-function type", + meth.id + ))); + } + res.push((meth.id.to_owned(), t)); + } + Ok(res) + } + + pub fn map_type(&self, t: &IDLType) -> Result { + match t { + IDLType::PrimT(prim) => Ok(map_prim(prim)), + IDLType::VarT(id) => { + self.find_type(id)?; + Ok(TypeInner::Var(id.to_string()).into()) + } + IDLType::OptT(t) => { + let t = self.map_type(t)?; + Ok(TypeInner::Opt(t).into()) + } + IDLType::VecT(t) => { + let t = self.map_type(t)?; + Ok(TypeInner::Vec(t).into()) + } + IDLType::RecordT(fs) => { + let fs = self.map_fields(fs)?; + Ok(TypeInner::Record(fs).into()) + } + IDLType::VariantT(fs) => { + let fs = self.map_fields(fs)?; + Ok(TypeInner::Variant(fs).into()) + } + IDLType::PrincipalT => Ok(TypeInner::Principal.into()), + IDLType::FuncT(func) => { + let mut t1 = Vec::new(); + for arg in func.args.iter() { + t1.push(self.map_arg(arg)?); + } + let mut t2 = Vec::new(); + for t in func.rets.iter() { + t2.push(self.map_type(t)?); + } + if func.modes.len() > 1 { + return Err(Error::msg("cannot have more than one mode")); + } + if func.modes.len() == 1 && func.modes[0] == FuncMode::Oneway && !t2.is_empty() { + return Err(Error::msg("oneway function has non-unit return type")); + } + let f = Function { + modes: func.modes.clone(), + args: t1, + rets: t2, + }; + Ok(TypeInner::Func(f).into()) + } + IDLType::ServT(ms) => { + let ms = self.map_meths(ms)?; + Ok(TypeInner::Service(ms).into()) + } + IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + IDLType::UnknownT => Err(Error::msg("unknown type")), + } + } } + impl std::fmt::Display for TypeEnv { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (k, v) in &self.0 { @@ -163,3 +257,26 @@ impl std::fmt::Display for TypeEnv { Ok(()) } } + +fn map_prim(prim: &PrimType) -> Type { + match prim { + PrimType::Nat => TypeInner::Nat, + PrimType::Nat8 => TypeInner::Nat8, + PrimType::Nat16 => TypeInner::Nat16, + PrimType::Nat32 => TypeInner::Nat32, + PrimType::Nat64 => TypeInner::Nat64, + PrimType::Int => TypeInner::Int, + PrimType::Int8 => TypeInner::Int8, + PrimType::Int16 => TypeInner::Int16, + PrimType::Int32 => TypeInner::Int32, + PrimType::Int64 => TypeInner::Int64, + PrimType::Float32 => TypeInner::Float32, + PrimType::Float64 => TypeInner::Float64, + PrimType::Bool => TypeInner::Bool, + PrimType::Text => TypeInner::Text, + PrimType::Null => TypeInner::Null, + PrimType::Reserved => TypeInner::Reserved, + PrimType::Empty => TypeInner::Empty, + } + .into() +} diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index 5e468005e..e7e8a5494 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -77,10 +77,9 @@ pub fn chase_type<'a>( /// Gather type definitions mentioned in actor, return the non-recursive type names in topological order. /// Recursive types can appear in any order. -pub fn chase_actor<'a>(env: &'a IDLEnv) -> Result> { +pub fn chase_actor<'a>(env: &'a IDLEnv, actor: &'a IDLType) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); - let actor = env.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; chase_type(&mut seen, &mut res, env, actor)?; Ok(res) } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 2e5052d0d..6ac594531 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -245,7 +245,7 @@ pub fn compile(env: &IDLEnv) -> String { doc.pretty(LINE_WIDTH).to_string() } Some(actor) => { - let def_list = chase_actor(env).unwrap(); + let def_list = chase_actor(env, actor).unwrap(); let recs = infer_rec(env, &def_list).unwrap(); let defs = pp_defs(env, &def_list, &recs); let init = if let IDLType::ClassT(ref args, _) = actor { diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index c40b3289f..4e6fc4a09 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -170,7 +170,7 @@ impl<'a> State<'a> { if self.tests.contains_key(use_type) { return; } - let def_list = chase_actor(self.state.env).unwrap(); + let def_list = chase_actor(self.state.env, src).unwrap(); let env = IDLEnv::from( self.state .env @@ -706,8 +706,8 @@ pub fn emit_bindgen(tree: &Config, env: &IDLEnv) -> (Output, Vec) { }; let env = state.nominalize_all(); let old_stats = state.state.stats.clone(); - let def_list = if env.actor.is_some() { - chase_actor(&env).unwrap() + let def_list = if let Some(actor) = &env.actor { + chase_actor(&env, actor).unwrap() } else { env.types_ids() }; diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 3fef3b17f..0fa3606bd 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,19 +1,18 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ syntax::{ - Binding, Dec, FuncType, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, PrimType, - TypeField, + Binding, Dec, FuncType, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, TypeField, }, - ArgType, Field, Function, Type, TypeEnv, TypeInner, + ArgType, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; pub struct Env<'a> { - pub te: &'a mut TypeEnv, - pub idl_env: &'a mut IDLEnv, - pub pre: bool, + te: &'a mut TypeEnv, + idl_env: &'a mut IDLEnv, + pre: bool, } impl Env<'_> { @@ -24,130 +23,7 @@ impl Env<'_> { /// Convert candid AST to internal Type pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { - let env = Env { - te: &mut env.clone(), - idl_env: &mut IDLEnv::new(), - pre: false, - }; - map_type(&env, ast) -} - -fn map_prim(prim: &PrimType) -> Type { - match prim { - PrimType::Nat => TypeInner::Nat, - PrimType::Nat8 => TypeInner::Nat8, - PrimType::Nat16 => TypeInner::Nat16, - PrimType::Nat32 => TypeInner::Nat32, - PrimType::Nat64 => TypeInner::Nat64, - PrimType::Int => TypeInner::Int, - PrimType::Int8 => TypeInner::Int8, - PrimType::Int16 => TypeInner::Int16, - PrimType::Int32 => TypeInner::Int32, - PrimType::Int64 => TypeInner::Int64, - PrimType::Float32 => TypeInner::Float32, - PrimType::Float64 => TypeInner::Float64, - PrimType::Bool => TypeInner::Bool, - PrimType::Text => TypeInner::Text, - PrimType::Null => TypeInner::Null, - PrimType::Reserved => TypeInner::Reserved, - PrimType::Empty => TypeInner::Empty, - } - .into() -} - -pub fn map_type(env: &Env, t: &IDLType) -> Result { - match t { - IDLType::PrimT(prim) => Ok(map_prim(prim)), - IDLType::VarT(id) => { - env.te.find_type(id)?; - Ok(TypeInner::Var(id.to_string()).into()) - } - IDLType::OptT(t) => { - let t = map_type(env, t)?; - Ok(TypeInner::Opt(t).into()) - } - IDLType::VecT(t) => { - let t = map_type(env, t)?; - Ok(TypeInner::Vec(t).into()) - } - IDLType::RecordT(fs) => { - let fs = map_fields(env, fs)?; - Ok(TypeInner::Record(fs).into()) - } - IDLType::VariantT(fs) => { - let fs = map_fields(env, fs)?; - Ok(TypeInner::Variant(fs).into()) - } - IDLType::PrincipalT => Ok(TypeInner::Principal.into()), - IDLType::FuncT(func) => { - let mut t1 = Vec::new(); - for arg in func.args.iter() { - t1.push(map_arg(env, arg)?); - } - let mut t2 = Vec::new(); - for t in func.rets.iter() { - t2.push(map_type(env, t)?); - } - if func.modes.len() > 1 { - return Err(Error::msg("cannot have more than one mode")); - } - if func.modes.len() == 1 - && func.modes[0] == candid::types::FuncMode::Oneway - && !t2.is_empty() - { - return Err(Error::msg("oneway function has non-unit return type")); - } - let f = Function { - modes: func.modes.clone(), - args: t1, - rets: t2, - }; - Ok(TypeInner::Func(f).into()) - } - IDLType::ServT(ms) => { - let ms = map_meths(env, ms)?; - Ok(TypeInner::Service(ms).into()) - } - IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - IDLType::UnknownT => Err(Error::msg("unknown type")), - } -} - -fn map_arg(env: &Env, arg: &IDLArgType) -> Result { - Ok(ArgType { - name: arg.name.clone(), - typ: map_type(env, &arg.typ)?, - }) -} - -fn map_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 = map_type(env, &f.typ)?; - let field = Field { - id: f.label.clone().into(), - ty, - }; - res.push(field); - } - Ok(res) -} - -fn map_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 = map_type(env, &meth.typ)?; - if !env.pre && env.te.as_func(&t).is_err() { - return Err(Error::msg(format!( - "method {} is a non-function type", - meth.id - ))); - } - res.push((meth.id.to_owned(), t)); - } - Ok(res) + env.map_type(ast).map_err(Error::msg) } pub fn check_type(env: &Env, t: &IDLType) -> Result { @@ -396,13 +272,12 @@ fn merge_actor( ))), Some(idl_type) => { let t = env.idl_env.trace_type(idl_type).map_err(Error::msg)?; - let idl_meths = env.idl_env.as_service(idl_type).map_err(Error::msg)?; match &t { IDLType::ClassT(_, _) => Err(Error::msg(format!( "Imported service file {file:?} has a service constructor" ))), IDLType::ServT(meths) => match &actor { - None => Ok(Some(t.clone())), + None => Ok(Some(t)), Some(idl_type) => { let t = env.idl_env.trace_type(idl_type).map_err(Error::msg)?; let serv = env.idl_env.as_service(&t).map_err(Error::msg)?; @@ -411,8 +286,7 @@ fn merge_actor( check_unique(ms.iter().map(|m| &m.id)).map_err(|e| { Error::msg(format!("Duplicate imported method name: {e}")) })?; - let res = - IDLType::ServT(idl_meths.iter().chain(meths.iter()).cloned().collect()); + let res = IDLType::ServT(ms); Ok(Some(res)) } }, diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 381eb406c..deee5191f 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -81,7 +81,14 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, }) } pub fn get_metadata(env: &IDLEnv) -> Option { - let def_list = crate::bindings::analysis::chase_actor(env).ok()?; + let serv = env.actor.clone()?; + let serv = env.trace_type(&serv).ok()?; + let serv = match &serv { + IDLType::ClassT(_, ty) => ty.as_ref(), + IDLType::ServT(_) => &serv, + _ => unreachable!(), + }; + let def_list = crate::bindings::analysis::chase_actor(env, &serv).ok()?; let mut filtered = IDLEnv::new(); for d in def_list { if let Ok((id, typ)) = env.find_binding(d) { @@ -91,6 +98,7 @@ pub fn get_metadata(env: &IDLEnv) -> Option { }); } } + filtered.set_actor(Some(serv.clone())); Some(candid::pretty::candid::compile(&filtered)) } From a5d9ef28652adcacd6b225d9caa22e2c2518ebe6 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 16:45:14 +0200 Subject: [PATCH 12/34] fix: candid parser --- rust/candid/src/types/type_env.rs | 118 +------------- rust/candid_parser/src/lib.rs | 8 +- rust/candid_parser/src/typing.rs | 144 ++++++++++++++++-- .../tests/assets/ok/example.d.ts | 30 ++-- .../candid_parser/tests/assets/ok/example.did | 28 ++-- rust/candid_parser/tests/assets/ok/example.mo | 2 +- rust/candid_parser/tests/assets/ok/example.rs | 4 +- 7 files changed, 169 insertions(+), 165 deletions(-) diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 4fca9f963..964d60dc0 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,5 +1,4 @@ -use crate::types::syntax::{Binding, IDLArgType, IDLType, PrimType, TypeField}; -use crate::types::{ArgType, Field, FuncMode, Function, Type, TypeInner}; +use crate::types::{Function, Type, TypeInner}; use crate::{Error, Result}; use std::collections::{BTreeMap, BTreeSet}; @@ -155,98 +154,6 @@ impl TypeEnv { } Ok(()) } - - fn map_arg(&self, arg: &IDLArgType) -> Result { - Ok(ArgType { - name: arg.name.clone(), - typ: self.map_type(&arg.typ)?, - }) - } - - fn map_fields(&self, fs: &[TypeField]) -> Result> { - // field label duplication is checked in the parser - let mut res = Vec::new(); - for f in fs.iter() { - let ty = self.map_type(&f.typ)?; - let field = Field { - id: f.label.clone().into(), - ty, - }; - res.push(field); - } - Ok(res) - } - - fn map_meths(&self, ms: &[Binding]) -> Result> { - // binding duplication is checked in the parser - let mut res = Vec::new(); - for meth in ms.iter() { - let t = self.map_type(&meth.typ)?; - if self.as_func(&t).is_err() { - return Err(Error::msg(format!( - "method {} is a non-function type", - meth.id - ))); - } - res.push((meth.id.to_owned(), t)); - } - Ok(res) - } - - pub fn map_type(&self, t: &IDLType) -> Result { - match t { - IDLType::PrimT(prim) => Ok(map_prim(prim)), - IDLType::VarT(id) => { - self.find_type(id)?; - Ok(TypeInner::Var(id.to_string()).into()) - } - IDLType::OptT(t) => { - let t = self.map_type(t)?; - Ok(TypeInner::Opt(t).into()) - } - IDLType::VecT(t) => { - let t = self.map_type(t)?; - Ok(TypeInner::Vec(t).into()) - } - IDLType::RecordT(fs) => { - let fs = self.map_fields(fs)?; - Ok(TypeInner::Record(fs).into()) - } - IDLType::VariantT(fs) => { - let fs = self.map_fields(fs)?; - Ok(TypeInner::Variant(fs).into()) - } - IDLType::PrincipalT => Ok(TypeInner::Principal.into()), - IDLType::FuncT(func) => { - let mut t1 = Vec::new(); - for arg in func.args.iter() { - t1.push(self.map_arg(arg)?); - } - let mut t2 = Vec::new(); - for t in func.rets.iter() { - t2.push(self.map_type(t)?); - } - if func.modes.len() > 1 { - return Err(Error::msg("cannot have more than one mode")); - } - if func.modes.len() == 1 && func.modes[0] == FuncMode::Oneway && !t2.is_empty() { - return Err(Error::msg("oneway function has non-unit return type")); - } - let f = Function { - modes: func.modes.clone(), - args: t1, - rets: t2, - }; - Ok(TypeInner::Func(f).into()) - } - IDLType::ServT(ms) => { - let ms = self.map_meths(ms)?; - Ok(TypeInner::Service(ms).into()) - } - IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - IDLType::UnknownT => Err(Error::msg("unknown type")), - } - } } impl std::fmt::Display for TypeEnv { @@ -257,26 +164,3 @@ impl std::fmt::Display for TypeEnv { Ok(()) } } - -fn map_prim(prim: &PrimType) -> Type { - match prim { - PrimType::Nat => TypeInner::Nat, - PrimType::Nat8 => TypeInner::Nat8, - PrimType::Nat16 => TypeInner::Nat16, - PrimType::Nat32 => TypeInner::Nat32, - PrimType::Nat64 => TypeInner::Nat64, - PrimType::Int => TypeInner::Int, - PrimType::Int8 => TypeInner::Int8, - PrimType::Int16 => TypeInner::Int16, - PrimType::Int32 => TypeInner::Int32, - PrimType::Int64 => TypeInner::Int64, - PrimType::Float32 => TypeInner::Float32, - PrimType::Float64 => TypeInner::Float64, - PrimType::Bool => TypeInner::Bool, - PrimType::Text => TypeInner::Text, - PrimType::Null => TypeInner::Null, - PrimType::Reserved => TypeInner::Reserved, - PrimType::Empty => TypeInner::Empty, - } - .into() -} diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 4086ac3c8..116bf3b65 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -47,7 +47,7 @@ //! ``` //! # fn f() -> anyhow::Result<()> { //! use candid::{TypeEnv, types::{Type, TypeInner}}; -//! use candid_parser::{check_prog, parse_idl_prog}; +//! use candid_parser::{check_prog, parse_idl_prog, typing::ast_to_type}; //! let did_file = r#" //! type List = opt record { head: int; tail: List }; //! type byte = nat8; @@ -65,7 +65,8 @@ //! // Or alternatively, use check_prog to check in-memory did file //! // Note that file import is ignored by check_prog. //! let mut env = TypeEnv::new(); -//! let actor: Type = check_prog(&mut env, &ast)?.unwrap(); +//! let actor = check_prog(&mut env, &ast)?.unwrap(); +//! let actor = ast_to_type(&env, &actor)?; //! //! let method = env.get_method(&actor, "g").unwrap(); //! assert_eq!(method.is_query(), true); @@ -86,7 +87,7 @@ //! use candid::{IDLArgs, types::value::IDLValue}; //! use candid_parser::parse_idl_args; //! # use candid::TypeEnv; -//! # use candid_parser::{check_prog, parse_idl_prog}; +//! # use candid_parser::{check_prog, parse_idl_prog, typing::ast_to_type}; //! # let did_file = r#" //! # type List = opt record { head: int; tail: List }; //! # type byte = nat8; @@ -98,6 +99,7 @@ //! # let ast = parse_idl_prog(did_file)?; //! # let mut env = TypeEnv::new(); //! # let actor = check_prog(&mut env, &ast)?.unwrap(); +//! # let actor = ast_to_type(&env, &actor)?; //! // Get method type f : (byte, int, nat, int8) -> (List) //! let method = env.get_method(&actor, "f").unwrap(); //! let args = parse_idl_args("(42, 42, 42, 42)")?; diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 0fa3606bd..15dba3318 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,9 +1,10 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ syntax::{ - Binding, Dec, FuncType, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, TypeField, + Binding, Dec, FuncType, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, PrimType, + TypeField, }, - ArgType, Type, TypeEnv, TypeInner, + ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; use std::collections::BTreeMap; @@ -15,15 +16,132 @@ pub struct Env<'a> { pre: bool, } -impl Env<'_> { - fn insert_binding(&mut self, binding: Binding) { - self.idl_env.insert_binding(binding); +/// Convert candid AST to internal Type +pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { + let env = Env { + te: &mut env.clone(), + idl_env: &mut IDLEnv::new(), + pre: false, + }; + map_type(&env, ast) +} + +fn map_prim(prim: &PrimType) -> Type { + match prim { + PrimType::Nat => TypeInner::Nat, + PrimType::Nat8 => TypeInner::Nat8, + PrimType::Nat16 => TypeInner::Nat16, + PrimType::Nat32 => TypeInner::Nat32, + PrimType::Nat64 => TypeInner::Nat64, + PrimType::Int => TypeInner::Int, + PrimType::Int8 => TypeInner::Int8, + PrimType::Int16 => TypeInner::Int16, + PrimType::Int32 => TypeInner::Int32, + PrimType::Int64 => TypeInner::Int64, + PrimType::Float32 => TypeInner::Float32, + PrimType::Float64 => TypeInner::Float64, + PrimType::Bool => TypeInner::Bool, + PrimType::Text => TypeInner::Text, + PrimType::Null => TypeInner::Null, + PrimType::Reserved => TypeInner::Reserved, + PrimType::Empty => TypeInner::Empty, } + .into() } -/// Convert candid AST to internal Type -pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { - env.map_type(ast).map_err(Error::msg) +pub fn map_type(env: &Env, t: &IDLType) -> Result { + match t { + IDLType::PrimT(prim) => Ok(map_prim(prim)), + IDLType::VarT(id) => { + env.te.find_type(id)?; + Ok(TypeInner::Var(id.to_string()).into()) + } + IDLType::OptT(t) => { + let t = map_type(env, t)?; + Ok(TypeInner::Opt(t).into()) + } + IDLType::VecT(t) => { + let t = map_type(env, t)?; + Ok(TypeInner::Vec(t).into()) + } + IDLType::RecordT(fs) => { + let fs = map_fields(env, fs)?; + Ok(TypeInner::Record(fs).into()) + } + IDLType::VariantT(fs) => { + let fs = map_fields(env, fs)?; + Ok(TypeInner::Variant(fs).into()) + } + IDLType::PrincipalT => Ok(TypeInner::Principal.into()), + IDLType::FuncT(func) => { + let mut t1 = Vec::new(); + for arg in func.args.iter() { + t1.push(map_arg(env, arg)?); + } + let mut t2 = Vec::new(); + for t in func.rets.iter() { + t2.push(map_type(env, t)?); + } + if func.modes.len() > 1 { + return Err(Error::msg("cannot have more than one mode")); + } + if func.modes.len() == 1 + && func.modes[0] == candid::types::FuncMode::Oneway + && !t2.is_empty() + { + return Err(Error::msg("oneway function has non-unit return type")); + } + let f = Function { + modes: func.modes.clone(), + args: t1, + rets: t2, + }; + Ok(TypeInner::Func(f).into()) + } + IDLType::ServT(ms) => { + let ms = map_meths(env, ms)?; + Ok(TypeInner::Service(ms).into()) + } + IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + IDLType::UnknownT => Err(Error::msg("unknown type")), + } +} + +fn map_arg(env: &Env, arg: &IDLArgType) -> Result { + Ok(ArgType { + name: arg.name.clone(), + typ: map_type(env, &arg.typ)?, + }) +} + +fn map_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 = map_type(env, &f.typ)?; + let field = Field { + id: f.label.clone().into(), + ty, + }; + res.push(field); + } + Ok(res) +} + +fn map_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 = map_type(env, &meth.typ)?; + if !env.pre && env.te.as_func(&t).is_err() { + return Err(Error::msg(format!( + "method {} is a non-function type", + meth.id + ))); + } + res.push((meth.id.to_owned(), t)); + } + Ok(res) } pub fn check_type(env: &Env, t: &IDLType) -> Result { @@ -128,11 +246,11 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { match dec { Dec::TypD(binding) => { - let t = check_type(env, &binding.typ)?; - env.insert_binding(Binding { - id: binding.id.to_owned(), - typ: t, - }); + let idl_type = check_type(env, &binding.typ)?; + let t = map_type(env, &idl_type)?; + let id = binding.id.to_string(); + env.te.0.insert(id.clone(), t); + env.idl_env.insert_binding(Binding { id, typ: idl_type }); } Dec::ImportType(_) | Dec::ImportServ(_) => (), } diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index a3975f37b..89620882c 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -4,14 +4,22 @@ import type { IDL } from '@dfinity/candid'; export type A = B; export type B = [] | [A]; -export type List = [] | [{ 'head' : bigint, 'tail' : List }]; +export interface node { 'head' : bigint, 'tail' : list } +export type list = [] | [node]; +export type tree = { + 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } + } | + { 'leaf' : bigint }; +export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } +export type t = ActorMethod<[Principal], undefined>; +export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; +export type b = [bigint, bigint]; export type a = { 'a' : null } | { 'b' : b }; -export type b = [bigint, bigint]; -export interface broker { 'find' : ActorMethod<[string], Principal> } -export type f = ActorMethod<[List, [Principal, string]], [[] | [List], res]>; -export type list = [] | [node]; export type my_type = Principal; +export type List = [] | [{ 'head' : bigint, 'tail' : List }]; +export type f = ActorMethod<[List, [Principal, string]], [[] | [List], res]>; +export interface broker { 'find' : ActorMethod<[string], Principal> } export interface nested { _0_ : bigint, _1_ : bigint, @@ -24,18 +32,10 @@ export interface nested { { 'C' : null }, _42_ : bigint, } -export type nested_res = { 'Ok' : { 'Ok' : null } | { 'Err' : null } } | - { 'Err' : { 'Ok' : { 'content' : string } } | { 'Err' : [bigint] } }; -export interface node { 'head' : bigint, 'tail' : list } export type res = { 'Ok' : [bigint, bigint] } | { 'Err' : { 'error' : string } }; -export interface s { 'f' : t, 'g' : ActorMethod<[list], [B, tree, stream]> } -export type stream = [] | [{ 'head' : bigint, 'next' : [Principal, string] }]; -export type t = ActorMethod<[Principal], undefined>; -export type tree = { - 'branch' : { 'val' : bigint, 'left' : tree, 'right' : tree } - } | - { 'leaf' : bigint }; +export type nested_res = { 'Ok' : { 'Ok' : null } | { 'Err' : null } } | + { 'Err' : { 'Ok' : { 'content' : string } } | { 'Err' : [bigint] } }; export interface _SERVICE { 'bbbbb' : ActorMethod<[b], undefined>, 'f' : t, diff --git a/rust/candid_parser/tests/assets/ok/example.did b/rust/candid_parser/tests/assets/ok/example.did index 6bd434f09..0767aee59 100644 --- a/rust/candid_parser/tests/assets/ok/example.did +++ b/rust/candid_parser/tests/assets/ok/example.did @@ -1,14 +1,22 @@ type A = B; type B = opt A; -type List = opt record { head : int; tail : List }; -type a = variant { a; b : b }; +type node = record { head : nat; tail : list }; +type list = opt node; +type tree = variant { + branch : record { val : int; left : tree; right : tree }; + leaf : int; +}; +type s = service { f : t; g : (list) -> (B, tree, stream) }; +type t = func (server : s) -> (); +type stream = opt record { head : nat; next : func () -> (stream) query }; type b = record { int; nat }; +type a = variant { a; b : b }; +type my_type = principal; +type List = opt record { head : int; tail : List }; +type f = func (List, func (int32) -> (int64)) -> (opt List, res); type broker = service { find : (name : text) -> (service { current : () -> (nat32); up : () -> () }); }; -type f = func (List, func (int32) -> (int64)) -> (opt List, res); -type list = opt node; -type my_type = principal; type nested = record { 0 : nat; 1 : nat; @@ -18,19 +26,11 @@ type nested = record { 41 : variant { 42; A; B; C }; 42 : nat; }; +type res = variant { Ok : record { int; nat }; Err : record { error : text } }; type nested_res = variant { Ok : variant { Ok; Err }; Err : variant { Ok : record { content : text }; Err : record { int } }; }; -type node = record { head : nat; tail : list }; -type res = variant { Ok : record { int; nat }; Err : record { error : text } }; -type s = service { f : t; g : (list) -> (B, tree, stream) }; -type stream = opt record { head : nat; next : func () -> (stream) query }; -type t = func (server : s) -> (); -type tree = variant { - branch : record { val : int; left : tree; right : tree }; - leaf : int; -}; service : { bbbbb : (b) -> (); f : t; diff --git a/rust/candid_parser/tests/assets/ok/example.mo b/rust/candid_parser/tests/assets/ok/example.mo index 15e37478c..3a909f1ca 100644 --- a/rust/candid_parser/tests/assets/ok/example.mo +++ b/rust/candid_parser/tests/assets/ok/example.mo @@ -44,8 +44,8 @@ module { public type Self = actor { bbbbb : shared b -> async (); f : t; - g : shared list -> async (B, tree, stream); f1 : shared (list, test : Blob, ?Bool) -> (); + g : shared list -> async (B, tree, stream); g1 : shared query (my_type, List, ?List, nested) -> async ( Int, broker, diff --git a/rust/candid_parser/tests/assets/ok/example.rs b/rust/candid_parser/tests/assets/ok/example.rs index c4a9d4002..14ec4611d 100644 --- a/rust/candid_parser/tests/assets/ok/example.rs +++ b/rust/candid_parser/tests/assets/ok/example.rs @@ -134,8 +134,8 @@ pub const service : Service = Service(CANISTER_ID); #[test] fn test_Arc_MyList_() { // Generated from ListInner.record.tail.use_type = "Arc" - let candid_src = r#"type List = opt ListInner; -type ListInner = record { head : int; tail : List }; + let candid_src = r#"type ListInner = record { head : int; tail : List }; +type List = opt ListInner; (List)"#; candid_parser::utils::check_rust_type::>(candid_src).unwrap(); } From 3144ce6dcacf78ab31c8da083694fe9abbc4c43b Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 18:41:45 +0200 Subject: [PATCH 13/34] refactor: remove "syntax" feature --- rust/candid/Cargo.toml | 3 +-- rust/candid/src/types/mod.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 88c405446..d6bb9140a 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -45,9 +45,8 @@ candid_parser = { path = "../candid_parser" } bignum = ["dep:num-bigint", "dep:num-traits"] printer = ["dep:pretty"] value = ["bignum", "printer"] -syntax = [] default = ["serde_bytes", "printer", "bignum"] -all = ["default", "value", "ic_principal/arbitrary", "syntax"] +all = ["default", "value", "ic_principal/arbitrary"] [[test]] name = "types" diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 92df03eb0..93852a7e7 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -9,7 +9,6 @@ use serde::ser::Error; mod impls; pub mod internal; pub mod subtype; -#[cfg(feature = "syntax")] pub mod syntax; pub mod type_env; #[cfg_attr(docsrs, doc(cfg(feature = "value")))] From f097c4cebcbf06b368ef6730d5fd2e7cead62674 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 18:44:54 +0200 Subject: [PATCH 14/34] chore: update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e8c4646..a7ae547b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ + `pp_args` and `pp_init_args` now require a `&[ArgType]` parameter. The `pp_rets` function has been added, with the signature of the old `pp_args`. * Non-breaking changes: - + The following structs have been moved from the `candid_parser` crate to the `candid::types::parser` module, under the `parser` feature flag: + + The following structs have been moved from the `candid_parser` crate to the `candid::types::syntax` module: - `IDLType` - `IDLTypes` - `PrimType` From a454e9811c2b04275d4a5c0de6af8979fd53e451 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Fri, 20 Jun 2025 19:15:42 +0200 Subject: [PATCH 15/34] fix: candid derive --- rust/candid/src/types/internal.rs | 103 +++++------ rust/candid/src/types/subtype.rs | 9 +- rust/candid/src/types/syntax.rs | 164 +++++++++++++++++- rust/candid_derive/src/func.rs | 29 ++-- rust/candid_parser/src/bindings/analysis.rs | 4 +- rust/candid_parser/src/bindings/javascript.rs | 8 +- rust/candid_parser/src/bindings/motoko.rs | 4 +- rust/candid_parser/src/bindings/rust.rs | 2 +- rust/candid_parser/src/random.rs | 2 +- rust/candid_parser/src/typing.rs | 6 +- rust/candid_parser/src/utils.rs | 4 +- 11 files changed, 254 insertions(+), 81 deletions(-) diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 9c4d47717..0b154d18a 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -1,5 +1,6 @@ use super::CandidType; use crate::idl_hash; +use crate::types::syntax::{Binding, FuncType, IDLArgType, IDLType, TypeField}; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BTreeMap; @@ -70,9 +71,9 @@ impl TypeName { } } -/// Used for `candid_derive::export_service` to generate `TypeEnv` from `Type`. +/// Used for `candid_derive::export_service` to generate `IDLEnv` from `IDLType`. /// -/// It performs a global rewriting of `Type` to resolve: +/// It performs a global rewriting of `IDLType` to resolve: /// * Duplicate type names in different modules/namespaces. /// * Give different names to instantiated polymorphic types. /// * Find the type name of a recursive node `Knot(TypeId)` and convert to `Var` node. @@ -83,99 +84,99 @@ impl TypeName { /// * Unless we do equivalence checking, recursive types can be unrolled and assigned to multiple names. #[derive(Default)] pub struct TypeContainer { - pub env: crate::TypeEnv, + pub idl_env: crate::types::syntax::IDLEnv, } impl TypeContainer { pub fn new() -> Self { TypeContainer { - env: crate::TypeEnv::new(), + idl_env: crate::types::syntax::IDLEnv::new(), } } - pub fn add(&mut self) -> Type { + pub fn add(&mut self) -> IDLType { let t = T::ty(); - self.go(&t) - } - fn go(&mut self, t: &Type) -> Type { - 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( + self.go(&t.into()) + } + fn go(&mut self, t: &IDLType) -> IDLType { + match t { + IDLType::OptT(t) => IDLType::OptT(Box::new(self.go(t))), + IDLType::VecT(t) => IDLType::VecT(Box::new(self.go(t))), + IDLType::RecordT(fs) => { + let res = IDLType::RecordT( fs.iter() - .map(|Field { id, ty }| Field { - id: id.clone(), - ty: self.go(ty), + .map(|TypeField { label, typ }| TypeField { + label: label.clone(), + typ: self.go(typ), }) .collect(), - ) - .into(); + ); if t.is_tuple() { return res; } - let id = ID.with(|n| n.borrow().get(t).cloned()); + let id = ID.with(|n| n.borrow().get(&t.clone().into()).cloned()); if let Some(id) = id { - self.env.0.insert(id.to_string(), res); - TypeInner::Var(id.to_string()) + self.idl_env.insert_binding(Binding { + id: id.to_string(), + typ: res, + }); + IDLType::VarT(id.to_string()) } else { // if the type is part of an enum, the id won't be recorded. // we want to inline the type in this case. - return res; + res } } - TypeInner::Variant(fs) => { - let res: Type = TypeInner::Variant( + IDLType::VariantT(fs) => { + let res = IDLType::VariantT( fs.iter() - .map(|Field { id, ty }| Field { - id: id.clone(), - ty: self.go(ty), + .map(|TypeField { label, typ }| TypeField { + label: label.clone(), + typ: self.go(typ), }) .collect(), - ) - .into(); - let id = ID.with(|n| n.borrow().get(t).cloned()); + ); + let id = ID.with(|n| n.borrow().get(&t.clone().into()).cloned()); if let Some(id) = id { - self.env.0.insert(id.to_string(), res); - TypeInner::Var(id.to_string()) + self.idl_env.insert_binding(Binding { + id: id.to_string(), + typ: res, + }); + IDLType::VarT(id.to_string()) } else { - return res; + res } } - TypeInner::Knot(id) => { - let name = id.to_string(); - let ty = ENV.with(|e| e.borrow().get(id).unwrap().clone()); - self.env.0.insert(id.to_string(), ty); - TypeInner::Var(name) - } - TypeInner::Func(func) => TypeInner::Func(Function { + IDLType::FuncT(func) => IDLType::FuncT(FuncType { modes: func.modes.clone(), args: func .args .iter() - .map(|arg| ArgType { + .map(|arg| IDLArgType { name: arg.name.clone(), typ: self.go(&arg.typ), }) .collect(), rets: func.rets.iter().map(|arg| self.go(arg)).collect(), }), - TypeInner::Service(serv) => TypeInner::Service( + IDLType::ServT(serv) => IDLType::ServT( serv.iter() - .map(|(id, t)| (id.clone(), self.go(t))) + .map(|binding| Binding { + id: binding.id.clone(), + typ: self.go(&binding.typ), + }) .collect(), ), - TypeInner::Class(inits, ref ty) => TypeInner::Class( + IDLType::ClassT(inits, ref ty) => IDLType::ClassT( inits .iter() - .map(|t| ArgType { + .map(|t| IDLArgType { name: t.name.clone(), typ: self.go(&t.typ), }) .collect(), - self.go(ty), + Box::new(self.go(ty)), ), t => t.clone(), } - .into() } } @@ -328,7 +329,7 @@ impl fmt::Display for Type { write!( f, "{}", - "TODO" // crate::pretty::candid::pp_ty(self).pretty(80), + crate::pretty::candid::pp_ty(&self.clone().into()).pretty(80), ) } } @@ -338,7 +339,7 @@ impl fmt::Display for TypeInner { write!( f, "{}", - "TODO" // crate::pretty::candid::pp_ty_inner(self).pretty(80), + crate::pretty::candid::pp_ty(&Type::from(self.clone()).into()).pretty(80), ) } } @@ -491,7 +492,7 @@ impl fmt::Display for Field { write!( f, "{}", - "TODO" // crate::pretty::candid::pp_field(self, false).pretty(80) + crate::pretty::candid::pp_field(&self.clone().into(), false).pretty(80) ) } } @@ -568,7 +569,7 @@ impl fmt::Display for Function { write!( f, "{}", - "TODO" // crate::pretty::candid::pp_function(self).pretty(80), + crate::pretty::candid::pp_function(&self.clone().into()).pretty(80), ) } } diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index fbd4c8ec8..7b0f1d614 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -318,5 +318,12 @@ fn pp_args(args: &[crate::types::ArgType]) -> String { #[cfg(feature = "printer")] fn pp_args(args: &[crate::types::ArgType]) -> String { use crate::pretty::candid::pp_args; - "TODO".to_string() // pp_args(args).pretty(80).to_string() + pp_args( + args.iter() + .map(|arg| arg.clone().into()) + .collect::>() + .as_slice(), + ) + .pretty(80) + .to_string() } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index c2241ad79..21352c6f4 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,6 +1,9 @@ use std::{collections::BTreeMap, fmt}; -use crate::types::{FuncMode, Label}; +use crate::{ + types::{ArgType, Field, FuncMode, Function, Label, Type, TypeInner}, + TypeEnv, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum IDLType { @@ -39,6 +42,100 @@ impl fmt::Display for IDLType { } } +impl From for Type { + fn from(t: IDLType) -> Self { + match t { + IDLType::PrimT(PrimType::Null) => TypeInner::Null, + IDLType::PrimT(PrimType::Bool) => TypeInner::Bool, + IDLType::PrimT(PrimType::Nat) => TypeInner::Nat, + IDLType::PrimT(PrimType::Int) => TypeInner::Int, + IDLType::PrimT(PrimType::Nat8) => TypeInner::Nat8, + IDLType::PrimT(PrimType::Nat16) => TypeInner::Nat16, + IDLType::PrimT(PrimType::Nat32) => TypeInner::Nat32, + IDLType::PrimT(PrimType::Nat64) => TypeInner::Nat64, + IDLType::PrimT(PrimType::Int8) => TypeInner::Int8, + IDLType::PrimT(PrimType::Int16) => TypeInner::Int16, + IDLType::PrimT(PrimType::Int32) => TypeInner::Int32, + IDLType::PrimT(PrimType::Int64) => TypeInner::Int64, + IDLType::PrimT(PrimType::Float32) => TypeInner::Float32, + IDLType::PrimT(PrimType::Float64) => TypeInner::Float64, + IDLType::PrimT(PrimType::Text) => TypeInner::Text, + IDLType::PrimT(PrimType::Reserved) => TypeInner::Reserved, + IDLType::PrimT(PrimType::Empty) => TypeInner::Empty, + IDLType::VarT(id) => TypeInner::Var(id), + IDLType::FuncT(func) => TypeInner::Func(func.into()), + IDLType::OptT(t) => TypeInner::Opt((*t).into()), + IDLType::VecT(t) => TypeInner::Vec((*t).into()), + IDLType::RecordT(fields) => { + TypeInner::Record(fields.into_iter().map(|t| t.into()).collect()) + } + IDLType::VariantT(fields) => { + TypeInner::Variant(fields.into_iter().map(|t| t.into()).collect()) + } + IDLType::ServT(methods) => { + TypeInner::Service(methods.into_iter().map(|t| (t.id, t.typ.into())).collect()) + } + IDLType::ClassT(args, t) => { + TypeInner::Class(args.into_iter().map(|t| t.into()).collect(), (*t).into()) + } + IDLType::PrincipalT => TypeInner::Principal, + IDLType::UnknownT => TypeInner::Unknown, + } + .into() + } +} + +impl From for IDLType { + fn from(t: Type) -> Self { + match t.as_ref() { + TypeInner::Null => IDLType::PrimT(PrimType::Null), + TypeInner::Bool => IDLType::PrimT(PrimType::Bool), + TypeInner::Nat => IDLType::PrimT(PrimType::Nat), + TypeInner::Int => IDLType::PrimT(PrimType::Int), + TypeInner::Nat8 => IDLType::PrimT(PrimType::Nat8), + TypeInner::Nat16 => IDLType::PrimT(PrimType::Nat16), + TypeInner::Nat32 => IDLType::PrimT(PrimType::Nat32), + TypeInner::Nat64 => IDLType::PrimT(PrimType::Nat64), + TypeInner::Int8 => IDLType::PrimT(PrimType::Int8), + TypeInner::Int16 => IDLType::PrimT(PrimType::Int16), + TypeInner::Int32 => IDLType::PrimT(PrimType::Int32), + TypeInner::Int64 => IDLType::PrimT(PrimType::Int64), + TypeInner::Float32 => IDLType::PrimT(PrimType::Float32), + TypeInner::Float64 => IDLType::PrimT(PrimType::Float64), + TypeInner::Text => IDLType::PrimT(PrimType::Text), + TypeInner::Reserved => IDLType::PrimT(PrimType::Reserved), + TypeInner::Empty => IDLType::PrimT(PrimType::Empty), + TypeInner::Var(id) => IDLType::VarT(id.to_string()), + TypeInner::Opt(t) => IDLType::OptT(Box::new(t.clone().into())), + TypeInner::Vec(t) => IDLType::VecT(Box::new(t.clone().into())), + TypeInner::Record(fields) => { + IDLType::RecordT(fields.iter().map(|t| t.clone().into()).collect()) + } + TypeInner::Variant(fields) => { + IDLType::VariantT(fields.iter().map(|t| t.clone().into()).collect()) + } + TypeInner::Func(func) => IDLType::FuncT(func.clone().into()), + TypeInner::Service(methods) => IDLType::ServT( + methods + .iter() + .map(|t| Binding { + id: t.0.clone(), + typ: t.1.clone().into(), + }) + .collect(), + ), + TypeInner::Class(args, t) => IDLType::ClassT( + args.iter().map(|t| t.clone().into()).collect(), + Box::new(t.clone().into()), + ), + TypeInner::Principal => IDLType::PrincipalT, + TypeInner::Unknown => IDLType::UnknownT, + TypeInner::Knot(_) => IDLType::UnknownT, + TypeInner::Future => IDLType::UnknownT, + } + } +} + #[derive(Debug, Clone)] pub struct IDLTypes { pub args: Vec, @@ -91,12 +188,49 @@ pub struct FuncType { pub rets: Vec, } +impl From for Function { + fn from(t: FuncType) -> Self { + Function { + modes: t.modes, + args: t.args.into_iter().map(|t| t.into()).collect(), + rets: t.rets.into_iter().map(|t| t.into()).collect(), + } + } +} + +impl From for FuncType { + fn from(t: Function) -> Self { + FuncType { + modes: t.modes, + args: t.args.into_iter().map(|t| t.into()).collect(), + rets: t.rets.into_iter().map(|t| t.into()).collect(), + } + } +} #[derive(Debug, Clone, PartialEq, Eq)] pub struct IDLArgType { pub typ: IDLType, pub name: Option, } +impl From for ArgType { + fn from(t: IDLArgType) -> Self { + ArgType { + typ: t.typ.into(), + name: t.name, + } + } +} + +impl From for IDLArgType { + fn from(t: ArgType) -> Self { + IDLArgType { + typ: t.typ.into(), + name: t.name, + } + } +} + impl IDLArgType { pub fn new(typ: IDLType) -> Self { Self { typ, name: None } @@ -121,6 +255,24 @@ pub struct TypeField { pub typ: IDLType, } +impl From for Field { + fn from(t: TypeField) -> Self { + Field { + id: t.label.into(), + ty: t.typ.into(), + } + } +} + +impl From for TypeField { + fn from(t: Field) -> Self { + TypeField { + label: (*t.id).clone(), + typ: t.ty.into(), + } + } +} + #[derive(Debug)] pub enum Dec { TypD(Binding), @@ -178,6 +330,16 @@ impl From> for IDLEnv { } } +impl From for IDLEnv { + fn from(env: TypeEnv) -> Self { + let mut idl_env = Self::default(); + for (id, t) in env.0 { + idl_env.insert_binding(Binding { id, typ: t.into() }); + } + idl_env + } +} + impl IDLEnv { pub fn new() -> Self { Self::default() diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index f3df9104c..dd5a40c32 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -84,7 +84,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { .map(|t| generate_arg(quote! { init_args }, t)) .collect::>(); quote! { - let mut init_args: Vec = Vec::new(); + let mut init_args: Vec = Vec::new(); #(#args)* } }); @@ -111,27 +111,30 @@ pub(crate) fn export_service(path: Option) -> TokenStream { }; quote! { { - let mut args: Vec = Vec::new(); + 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())); + let func = FuncType { args, rets, modes: #modes }; + service.push(Binding { + id: #name.to_string(), + typ: IDLType::FuncT(func), + }); } } }); let service = quote! { - use #candid::types::{CandidType, Function, Type, ArgType, TypeInner}; - let mut service = Vec::<(String, Type)>::new(); + use #candid::types::{CandidType, syntax::{IDLEnv, IDLArgType, FuncType, IDLType, Binding}}; + let mut service = Vec::::new(); let mut env = #candid::types::internal::TypeContainer::new(); #(#gen_tys)* - service.sort_unstable_by_key(|(name, _)| name.clone()); - let ty = TypeInner::Service(service).into(); + service.sort_unstable_by_key(|b| b.id.clone()); + let ty = IDLType::ServT(service); }; let actor = if let Some(init) = init { quote! { #init - let actor = Some(TypeInner::Class(init_args, ty).into()); + let actor = Some(IDLType::ClassT(init_args, Box::new(ty))); } } else { quote! { let actor = Some(ty); } @@ -140,8 +143,8 @@ pub(crate) fn export_service(path: Option) -> TokenStream { fn __export_service() -> String { #service #actor - // let result = #candid::pretty::candid::compile(&env.env, &actor); - let result = "TODO"; + env.idl_env.set_actor(actor); + let result = #candid::pretty::candid::compile(&env.idl_env); format!("{}", result) } }; @@ -160,7 +163,7 @@ fn generate_arg(name: TokenStream, (arg_name, ty): &(Option, String)) -> .unwrap_or(quote! { None }); let ty = syn::parse_str::(ty.as_str()).unwrap(); quote! { - #name.push(ArgType { name: #arg_name, typ: env.add::<#ty>() }); + #name.push(IDLArgType { name: #arg_name, typ: env.add::<#ty>() }); } } diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index e7e8a5494..6c24759ad 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -84,10 +84,10 @@ pub fn chase_actor<'a>(env: &'a IDLEnv, actor: &'a IDLType) -> Result(env: &'a IDLEnv) -> Result>> { +pub fn chase_def_use(env: &IDLEnv) -> Result>> { let mut res = BTreeMap::new(); let actor = env.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; - let actor = env.trace_type(&actor).map_err(Error::msg)?; + let actor = env.trace_type(actor).map_err(Error::msg)?; if let IDLType::ClassT(args, _) = &actor { for (i, arg) in args.iter().enumerate() { let mut used = Vec::new(); diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 6ac594531..f6965475f 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -175,7 +175,7 @@ fn pp_args(args: &[IDLArgType]) -> RcDoc { enclose("[", doc, "]") } -fn pp_rets<'a>(args: &'a [IDLType]) -> RcDoc<'a> { +fn pp_rets(args: &[IDLType]) -> RcDoc { let doc = concat(args.iter().map(pp_ty), ","); enclose("[", doc, "]") } @@ -422,13 +422,13 @@ import { Principal } from './principal'; use HostAssert::*; let test_func = match cmd { Encode(args, tys, _, _) | NotEncode(args, tys) => { - let idl_tys = to_idl_types(&tys); + let idl_tys = to_idl_types(tys); let items = [super::pp_rets(&[]), pp_encode(args, &[])]; let params = concat(items.iter().cloned(), ","); str("IDL.decode").append(enclose("(", params, ")")) } Decode(bytes, tys, _, _) | NotDecode(bytes, tys) => { - let idl_tys = to_idl_types(&tys); + let idl_tys = to_idl_types(tys); pp_decode(bytes, &[]) } }; @@ -443,7 +443,7 @@ import { Principal } from './principal'; }; let expected = match cmd { Encode(_, tys, _, bytes) => { - let idl_tys = to_idl_types(&tys); + let idl_tys = to_idl_types(tys); pp_decode(bytes, &[]) } Decode(_, _, _, vals) => value::pp_args(vals), diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 0e0889c70..3368faf07 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -255,9 +255,9 @@ fn pp_service(serv: &[Binding]) -> RcDoc { fn pp_defs<'a>(bindings: &[(&'a str, &'a IDLType)]) -> RcDoc<'a> { lines(bindings.iter().map(|(id, typ)| { kwd("public type") - .append(escape(&id, false)) + .append(escape(id, false)) .append(" = ") - .append(pp_ty(&typ)) + .append(pp_ty(typ)) .append(";") })) } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 4e6fc4a09..dfdddc150 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -1016,7 +1016,7 @@ impl NominalState<'_> { let lab = id.to_string(); let old = self.state.push_state(&StateElem::Label(&lab)); path.push(TypePath::Id(lab.clone())); - let ty = self.nominalize(env, path, &typ); + let ty = self.nominalize(env, path, typ); path.pop(); self.state.pop_state(old, StateElem::Label(&lab)); Binding { id: lab, typ: ty } diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index 445fdd2e1..dce64952b 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -126,7 +126,7 @@ impl RandState<'_> { } let res = Ok(match ty { IDLType::VarT(id) => { - let ty = self.0.env.rec_find_type(id).map_err(|e| Error::msg(e))?; + let ty = self.0.env.rec_find_type(id).map_err(Error::msg)?; self.any(u, ty)? } IDLType::PrimT(PrimType::Null) => IDLValue::Null, diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 15dba3318..365cf0751 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -215,8 +215,8 @@ fn check_fields(env: &Env, fs: &[TypeField]) -> Result> { for f in fs.iter() { let typ = check_type(env, &f.typ).map_err(Error::msg)?; let field = TypeField { - label: f.label.clone().into(), - typ: typ, + label: f.label.clone(), + typ, }; res.push(field); } @@ -371,7 +371,7 @@ pub fn check_init_args( args.push(check_arg(&env, arg).and_then(|t| { Ok(ArgType { name: t.name, - typ: ast_to_type(&env.te, &t.typ)?, + typ: ast_to_type(env.te, &t.typ)?, }) })?); } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index deee5191f..832cd8512 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -88,7 +88,7 @@ pub fn get_metadata(env: &IDLEnv) -> Option { IDLType::ServT(_) => &serv, _ => unreachable!(), }; - let def_list = crate::bindings::analysis::chase_actor(env, &serv).ok()?; + let def_list = crate::bindings::analysis::chase_actor(env, serv).ok()?; let mut filtered = IDLEnv::new(); for d in def_list { if let Ok((id, typ)) = env.find_binding(d) { @@ -136,7 +136,7 @@ pub fn check_rust_type(candid_args: &str) -> Result<()> { let args = check_init_args(&mut env, &TypeEnv::new(), &mut idl_env, &parsed)?; let mut rust_env = TypeContainer::new(); let ty = rust_env.add::(); - let ty = env.merge_type(rust_env.env, ty); + let ty = env.merge_type(env.clone(), ty.into()); let mut gamma = std::collections::HashSet::new(); equal(&mut gamma, &env, &args[0].typ, &ty)?; Ok(()) From 7199405ef43e7dd9fdb65b8c63d4a337add98d26 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 11:59:48 +0200 Subject: [PATCH 16/34] fix: introduce knot for recursive types --- rust/candid/src/pretty/candid.rs | 2 +- rust/candid/src/types/syntax.rs | 28 +++++++++++++++---- rust/candid/tests/types.rs | 15 +++++----- rust/candid_derive/src/func.rs | 2 +- rust/candid_parser/src/bindings/javascript.rs | 2 +- rust/candid_parser/src/bindings/motoko.rs | 2 +- rust/candid_parser/src/bindings/rust.rs | 2 +- rust/candid_parser/src/bindings/typescript.rs | 2 +- rust/candid_parser/src/configs.rs | 2 +- rust/candid_parser/src/typing.rs | 4 +-- rust/candid_parser/src/utils.rs | 2 +- 11 files changed, 39 insertions(+), 24 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 14ed8c235..83082e8ee 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -125,7 +125,7 @@ pub fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } - UnknownT => unreachable!(), + KnotT(ref id) => RcDoc::text(format!("{id}")), } } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 21352c6f4..fbb1f1879 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, fmt}; use crate::{ - types::{ArgType, Field, FuncMode, Function, Label, Type, TypeInner}, + types::{ArgType, Field, FuncMode, Function, Label, Type, TypeId, TypeInner}, TypeEnv, }; @@ -17,7 +17,8 @@ pub enum IDLType { ServT(Vec), ClassT(Vec, Box), PrincipalT, - UnknownT, + /// Used for Rust recursive types. + KnotT(TypeId), } impl IDLType { @@ -79,7 +80,7 @@ impl From for Type { TypeInner::Class(args.into_iter().map(|t| t.into()).collect(), (*t).into()) } IDLType::PrincipalT => TypeInner::Principal, - IDLType::UnknownT => TypeInner::Unknown, + IDLType::KnotT(id) => TypeInner::Knot(id), } .into() } @@ -129,9 +130,10 @@ impl From for IDLType { Box::new(t.clone().into()), ), TypeInner::Principal => IDLType::PrincipalT, - TypeInner::Unknown => IDLType::UnknownT, - TypeInner::Knot(_) => IDLType::UnknownT, - TypeInner::Future => IDLType::UnknownT, + TypeInner::Knot(id) => IDLType::KnotT(id.clone()), + TypeInner::Unknown | TypeInner::Future => { + panic!("Unknown type: {:?}", t) + } } } } @@ -340,6 +342,20 @@ impl From for IDLEnv { } } +impl From for TypeEnv { + fn from(env: IDLEnv) -> Self { + let mut type_env = Self::default(); + for (id, t) in env + .types + .iter() + .filter_map(|(id, t)| t.as_ref().map(|t| (id, t))) + { + type_env.0.insert(id.to_string(), t.clone().into()); + } + type_env + } +} + impl IDLEnv { pub fn new() -> Self { Self::default() diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index 9c8fcb965..d0eec1aef 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -239,7 +239,12 @@ fn test_func() { fn init(_: List) {} candid::export_service!(); - let expected = r#"type A = variant { + let expected = r#"type List = record { head : nat8; tail : opt List }; +type Result = variant { Ok : List; Err : empty }; +type NamedStruct = record { a : nat16; b : int32 }; +type List_1 = record { head : int8; tail : opt List_1 }; +type Wrap = record { head : int8; tail : opt Box }; +type A = variant { A1 : record { List_1; Wrap; Wrap }; A2 : record { text; principal }; A3 : int; @@ -248,14 +253,8 @@ fn test_func() { A6 : NamedStruct; A7 : record { b : int32; c : nat16 }; }; -type Box = record { head : int8; tail : opt Box }; -type List = record { head : nat8; tail : opt List }; -type List_1 = record { head : int8; tail : opt List_1 }; -type List_2 = record { head : int; tail : opt List_2 }; -type NamedStruct = record { a : nat16; b : int32 }; -type Result = variant { Ok : List; Err : empty }; type Result_1 = variant { Ok : record { record { A }; A }; Err : text }; -type Wrap = record { head : int8; tail : opt Box }; +type List_2 = record { head : int; tail : opt List_2 }; service : (List_2) -> { id_struct : (record { List }) -> (Result) query; id_struct_composite : (record { List }) -> (Result) composite_query; diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index dd5a40c32..4bd8a4533 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -124,7 +124,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { } }); let service = quote! { - use #candid::types::{CandidType, syntax::{IDLEnv, IDLArgType, FuncType, IDLType, Binding}}; + use #candid::types::syntax::{IDLArgType, FuncType, IDLType, Binding}; let mut service = Vec::::new(); let mut env = #candid::types::internal::TypeContainer::new(); #(#gen_tys)* diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index f6965475f..90634a20c 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -136,7 +136,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), FuncT(ref func) => str("IDL.Func").append(pp_function(func)), ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), - ClassT(_, _) | UnknownT => unreachable!(), + ClassT(_, _) | KnotT(_) => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 3368faf07..980e9ecec 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -147,7 +147,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } - UnknownT => unreachable!(), + KnotT(_) => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index dfdddc150..48e10ceac 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -287,7 +287,7 @@ fn test_{test_name}() {{ } FuncT(_) => unreachable!(), // not possible after rewriting ServT(_) => unreachable!(), // not possible after rewriting - ClassT(_, _) | UnknownT => unreachable!(), + ClassT(_, _) | KnotT(_) => unreachable!(), } }; 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 0a6b19d73..67f3e1fa6 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -104,7 +104,7 @@ fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { } FuncT(_) => str("[Principal, string]"), ServT(_) => str("Principal"), - ClassT(_, _) | UnknownT => unreachable!(), + ClassT(_, _) | KnotT(_) => unreachable!(), } } diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index ec1361991..6a063258a 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -408,7 +408,7 @@ fn path_name(t: &IDLType) -> String { IDLType::FuncT(_) => "func", IDLType::ServT(_) => "service", IDLType::ClassT(..) => "func:init", - IDLType::UnknownT => unreachable!(), + IDLType::KnotT(id) => id.name, } .to_string() } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 365cf0751..07290ea36 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -102,8 +102,8 @@ pub fn map_type(env: &Env, t: &IDLType) -> Result { let ms = map_meths(env, ms)?; Ok(TypeInner::Service(ms).into()) } + IDLType::KnotT(id) => Ok(TypeInner::Knot(id.clone()).into()), IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - IDLType::UnknownT => Err(Error::msg("unknown type")), } } @@ -198,7 +198,7 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { Ok(IDLType::ServT(ms)) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - IDLType::UnknownT => Err(Error::msg("unknown type")), + IDLType::KnotT(_) => Err(Error::msg("knot type not supported")), } } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 832cd8512..0e2e116c0 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -136,7 +136,7 @@ pub fn check_rust_type(candid_args: &str) -> Result<()> { let args = check_init_args(&mut env, &TypeEnv::new(), &mut idl_env, &parsed)?; let mut rust_env = TypeContainer::new(); let ty = rust_env.add::(); - let ty = env.merge_type(env.clone(), ty.into()); + let ty = env.merge_type(rust_env.idl_env.into(), ty.into()); let mut gamma = std::collections::HashSet::new(); equal(&mut gamma, &env, &args[0].typ, &ty)?; Ok(()) From 2e3d93f939b546159906534b27f49761173632fd Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 13:25:00 +0200 Subject: [PATCH 17/34] fix: navigate knot types --- rust/candid/src/types/internal.rs | 11 +- rust/candid/src/types/syntax.rs | 4 + rust/candid/tests/types.rs | 178 +++++++++++++++++++++++++++--- tools/didc/src/main.rs | 4 +- 4 files changed, 180 insertions(+), 17 deletions(-) diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 0b154d18a..d09326c0f 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -145,6 +145,15 @@ impl TypeContainer { res } } + IDLType::KnotT(id) => { + let name = id.to_string(); + let ty = ENV.with(|e| e.borrow().get(id).unwrap().clone()); + self.idl_env.insert_binding(Binding { + id: name.clone(), + typ: ty.into(), + }); + IDLType::VarT(name) + } IDLType::FuncT(func) => IDLType::FuncT(FuncType { modes: func.modes.clone(), args: func @@ -707,7 +716,7 @@ pub fn find_type(id: &TypeId) -> Option { // only for debugging #[allow(dead_code)] -pub(crate) fn show_env() { +pub fn show_env() { ENV.with(|e| println!("{:?}", e.borrow())); } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index fbb1f1879..6e71ac5a0 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -435,6 +435,10 @@ impl IDLEnv { pub fn trace_type(&self, t: &IDLType) -> Result { match t { IDLType::VarT(id) => self.trace_type(self.find_type(id)?), + IDLType::KnotT(id) => { + let t = crate::types::internal::find_type(id).unwrap(); + self.trace_type(&t.into()) + } _ => Ok(t.clone()), } } diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index d0eec1aef..ed42c5332 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -5,8 +5,12 @@ use std::fmt::Debug; use candid::{ candid_method, record, ser::IDLBuilder, - types::value::{IDLValue, IDLValueVisitor}, - types::{get_type, Serializer, Type, TypeInner}, + types::{ + get_type, + syntax::{IDLType, PrimType, TypeField}, + value::{IDLValue, IDLValueVisitor}, + Label, Serializer, Type, TypeInner, + }, variant, CandidType, Decode, Deserialize, Encode, Int, }; use serde::de::DeserializeOwned; @@ -84,22 +88,40 @@ fn any_val() { #[test] fn test_primitive() { - assert_eq!(get_type(&true), TypeInner::Bool.into()); - assert_eq!(get_type(&Box::new(42)), TypeInner::Int32.into()); - assert_eq!(get_type(&Box::new(Int::from(42))), TypeInner::Int.into()); + let bool_prim = get_type(&true); + assert_eq!(bool_prim, TypeInner::Bool.into()); + assert_eq!(IDLType::from(bool_prim), IDLType::PrimT(PrimType::Bool)); + + let null_prim = get_type(&()); + assert_eq!(null_prim, TypeInner::Null.into()); + assert_eq!(IDLType::from(null_prim), IDLType::PrimT(PrimType::Null)); + + let int_prim = get_type(&Box::new(42)); + assert_eq!(int_prim, TypeInner::Int32.into()); + assert_eq!(IDLType::from(int_prim), IDLType::PrimT(PrimType::Int32)); + + let int_prim = get_type(&Box::new(Int::from(42))); + assert_eq!(int_prim, TypeInner::Int.into()); + assert_eq!(IDLType::from(int_prim), IDLType::PrimT(PrimType::Int)); + let opt: Option<&str> = None; + let opt_prim = get_type(&opt); + assert_eq!(opt_prim, TypeInner::Opt(TypeInner::Text.into()).into()); assert_eq!( - get_type(&opt), - TypeInner::Opt(TypeInner::Text.into()).into() - ); - assert_eq!( - get_type(&[0, 1, 2, 3]), - TypeInner::Vec(TypeInner::Int32.into()).into() + IDLType::from(opt_prim), + IDLType::OptT(Box::new(IDLType::PrimT(PrimType::Text))) ); + + let vec_prim = get_type(&[0, 1, 2, 3]); + assert_eq!(vec_prim, TypeInner::Vec(TypeInner::Int32.into()).into()); assert_eq!( - get_type(&std::marker::PhantomData::), - TypeInner::Nat32.into() + IDLType::from(vec_prim), + IDLType::VecT(Box::new(IDLType::PrimT(PrimType::Int32))) ); + + let nat_prim = get_type(&std::marker::PhantomData::); + assert_eq!(nat_prim, TypeInner::Nat32.into()); + assert_eq!(IDLType::from(nat_prim), IDLType::PrimT(PrimType::Nat32)); } #[test] @@ -107,6 +129,8 @@ fn test_struct() { #[derive(Debug, CandidType)] struct Newtype(Int); assert_eq!(Newtype::ty(), TypeInner::Int.into()); + assert_eq!(IDLType::from(Newtype::ty()), IDLType::PrimT(PrimType::Int)); + #[derive(Debug, CandidType)] struct A { foo: Int, @@ -116,6 +140,19 @@ fn test_struct() { A::ty(), record! { foo: TypeInner::Int.into(); bar: TypeInner::Bool.into() } ); + assert_eq!( + IDLType::from(A::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("bar".to_string()), + typ: IDLType::PrimT(PrimType::Bool), + }, + TypeField { + label: Label::Named("foo".to_string()), + typ: IDLType::PrimT(PrimType::Int), + }, + ]) + ); #[derive(Debug, CandidType)] struct G { @@ -127,6 +164,19 @@ fn test_struct() { get_type(&res), record! { g1: TypeInner::Int32.into(); g2: TypeInner::Bool.into() } ); + assert_eq!( + IDLType::from(get_type(&res)), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("g1".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("g2".to_string()), + typ: IDLType::PrimT(PrimType::Bool), + }, + ]) + ); #[derive(Debug, CandidType)] struct List { @@ -137,6 +187,68 @@ fn test_struct() { List::ty(), record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::()).into()).into() } ); + assert_eq!( + IDLType::from(List::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("head".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("tail".to_string()), + typ: IDLType::OptT(Box::new( + IDLType::KnotT(candid::types::TypeId::of::()) + )), + }, + ]) + ); + + #[derive(Debug, CandidType)] + struct GenericList { + head: T, + tail: Option>>, + } + assert_eq!( + GenericList::::ty(), + record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::>()).into()).into() } + ); + assert_eq!( + IDLType::from(GenericList::::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("head".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("tail".to_string()), + typ: IDLType::OptT(Box::new(IDLType::KnotT(candid::types::TypeId::of::< + GenericList, + >()))), + }, + ]) + ); + + #[derive(Debug, CandidType)] + struct Wrap(GenericList); + assert_eq!( + Wrap::ty(), + record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::>()).into()).into() } + ); + assert_eq!( + IDLType::from(Wrap::ty()), + IDLType::RecordT(vec![ + TypeField { + label: Label::Named("head".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("tail".to_string()), + typ: IDLType::OptT(Box::new(IDLType::KnotT(candid::types::TypeId::of::< + GenericList, + >()))), + }, + ]) + ); } #[test] @@ -160,6 +272,45 @@ fn test_variant() { Newtype: TypeInner::Bool.into(); } ); + assert_eq!( + IDLType::from(get_type(&v)), + IDLType::VariantT(vec![ + TypeField { + label: Label::Named("Bar".to_string()), + typ: IDLType::RecordT(vec![ + TypeField { + label: Label::Id(0), + typ: IDLType::PrimT(PrimType::Bool), + }, + TypeField { + label: Label::Id(1), + typ: IDLType::PrimT(PrimType::Int32), + }, + ]), + }, + TypeField { + label: Label::Named("Baz".to_string()), + typ: IDLType::RecordT(vec![ + TypeField { + label: Label::Named("a".to_string()), + typ: IDLType::PrimT(PrimType::Int32), + }, + TypeField { + label: Label::Named("b".to_string()), + typ: IDLType::PrimT(PrimType::Nat32), + }, + ]), + }, + TypeField { + label: Label::Named("Foo".to_string()), + typ: IDLType::PrimT(PrimType::Null), + }, + TypeField { + label: Label::Named("Newtype".to_string()), + typ: IDLType::PrimT(PrimType::Bool), + }, + ]) + ); } #[derive(CandidType)] @@ -243,6 +394,7 @@ fn test_func() { type Result = variant { Ok : List; Err : empty }; type NamedStruct = record { a : nat16; b : int32 }; type List_1 = record { head : int8; tail : opt List_1 }; +type Box = record { head : int8; tail : opt Box }; type Wrap = record { head : int8; tail : opt Box }; type A = variant { A1 : record { List_1; Wrap; Wrap }; diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 89dc42da9..5136b9083 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -148,9 +148,7 @@ impl TypeAnnotation { (None, Some(meth)) => { let actor = actor .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; - let func = idl_env - .get_method(&actor, meth) - .map_err(|e| Error::msg(e))?; + let func = idl_env.get_method(&actor, meth).map_err(Error::msg)?; match mode { Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), Mode::Decode => func.rets.clone(), From d72949594613a39117e55666a6275e0024721326 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 13:26:45 +0200 Subject: [PATCH 18/34] fix: visibility --- rust/candid/src/types/internal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index d09326c0f..8d30d641f 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -716,7 +716,7 @@ pub fn find_type(id: &TypeId) -> Option { // only for debugging #[allow(dead_code)] -pub fn show_env() { +pub(crate) fn show_env() { ENV.with(|e| println!("{:?}", e.borrow())); } From 6aec2778cf2182822ba53d502dc3db8d1f1eaeca Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 13:45:59 +0200 Subject: [PATCH 19/34] refactor: group functions in env --- rust/candid/src/types/syntax.rs | 6 +++--- rust/candid_parser/src/typing.rs | 30 ++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 6e71ac5a0..ecdc96288 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -370,10 +370,10 @@ impl IDLEnv { } } - pub fn insert_unknown(&mut self, id: &str) { - let duplicate = self.types.insert(id.to_string(), None); + pub fn insert_unknown(&mut self, id: String) { + let duplicate = self.types.insert(id.clone(), None); if duplicate.is_none() { - self.types_bindings_ids.push(id.to_string()); + self.types_bindings_ids.push(id); } } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 07290ea36..64fb4bf23 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -16,6 +16,22 @@ pub struct Env<'a> { pre: bool, } +impl Env<'_> { + fn insert_type(&mut self, id: String, t: Type, binding: IDLType) { + self.te.0.insert(id.clone(), t); + self.idl_env.insert_binding(Binding { id, typ: binding }); + } + + fn visit_id(&mut self, id: String) -> Result<()> { + let duplicate = self.te.0.insert(id.clone(), TypeInner::Unknown.into()); + if duplicate.is_some() { + return Err(Error::msg(format!("duplicate binding for {id}"))); + } + self.idl_env.insert_unknown(id); + Ok(()) + } +} + /// Convert candid AST to internal Type pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { let env = Env { @@ -245,12 +261,10 @@ 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) => { - let idl_type = check_type(env, &binding.typ)?; + Dec::TypD(Binding { id, typ }) => { + let idl_type = check_type(env, typ)?; let t = map_type(env, &idl_type)?; - let id = binding.id.to_string(); - env.te.0.insert(id.clone(), t); - env.idl_env.insert_binding(Binding { id, typ: idl_type }); + env.insert_type(id.to_string(), t, idl_type); } Dec::ImportType(_) | Dec::ImportServ(_) => (), } @@ -261,11 +275,7 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, typ: _ }) = 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}"))); - } - env.idl_env.insert_unknown(id); + env.visit_id(id.to_string())?; } } env.pre = true; From 4d180a3298dfd1f7e76694026e406e9f5cab4e64 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 13:50:57 +0200 Subject: [PATCH 20/34] refactor: is_null helper --- rust/candid/src/pretty/candid.rs | 2 +- rust/candid/src/types/syntax.rs | 4 ++++ rust/candid_parser/src/bindings/motoko.rs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 83082e8ee..b59f9c415 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -137,7 +137,7 @@ pub fn pp_label(id: &Label) -> RcDoc { } pub(crate) fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc { - let ty_doc = if is_variant && field.typ == IDLType::PrimT(PrimType::Null) { + let ty_doc = if is_variant && field.typ.is_null() { RcDoc::nil() } else { kwd(" :").append(pp_ty(&field.typ)) diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index ecdc96288..ad78a9e28 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -35,6 +35,10 @@ impl IDLType { _ => false, } } + + pub fn is_null(&self) -> bool { + matches!(self, IDLType::PrimT(PrimType::Null)) + } } impl fmt::Display for IDLType { diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 980e9ecec..6fd69f47b 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -168,7 +168,7 @@ fn pp_field(field: &TypeField) -> RcDoc { } fn pp_variant(field: &TypeField) -> RcDoc { let doc = str("#").append(pp_label(&field.label)); - if field.typ != IDLType::PrimT(PrimType::Null) { + if !field.typ.is_null() { doc.append(" : ").append(pp_ty(&field.typ)) } else { doc From 8aacfa04f2f82336af7e9c1d357ef34eb28919a0 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 14:08:18 +0200 Subject: [PATCH 21/34] refactor: rename variables --- rust/candid_parser/src/typing.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 64fb4bf23..1bcd1cb2e 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -17,12 +17,12 @@ pub struct Env<'a> { } impl Env<'_> { - fn insert_type(&mut self, id: String, t: Type, binding: IDLType) { + fn insert_type(&mut self, id: String, t: Type, idl_type: IDLType) { self.te.0.insert(id.clone(), t); - self.idl_env.insert_binding(Binding { id, typ: binding }); + self.idl_env.insert_binding(Binding { id, typ: idl_type }); } - fn visit_id(&mut self, id: String) -> Result<()> { + fn insert_id(&mut self, id: String) -> Result<()> { let duplicate = self.te.0.insert(id.clone(), TypeInner::Unknown.into()); if duplicate.is_some() { return Err(Error::msg(format!("duplicate binding for {id}"))); @@ -275,7 +275,7 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, typ: _ }) = dec { - env.visit_id(id.to_string())?; + env.insert_id(id.to_string())?; } } env.pre = true; From 53f58ecc41949bc6a4c0a439b92bc865ed9f00f9 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 15:34:49 +0200 Subject: [PATCH 22/34] fix: types mapping --- rust/bench/bench.rs | 5 +++-- rust/candid_parser/src/typing.rs | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 5f6e0272d..3f84e1e26 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -1,5 +1,6 @@ use canbench_rs::{bench, bench_fn, bench_scope, BenchResult}; use candid::{CandidType, Decode, DecoderConfig, Deserialize, Encode, Int, Nat}; +use candid_parser::typing::ast_to_type; use std::collections::BTreeMap; #[allow(clippy::all)] @@ -212,9 +213,9 @@ fn nns() -> BenchResult { "#; bench_fn(|| { let _p = bench_scope("0. Parsing"); - let (env, serv) = nns_did.load().unwrap(); + let (env, _, serv) = nns_did.load().unwrap(); let args = candid_parser::parse_idl_args(motion_proposal).unwrap(); - let serv = serv.unwrap(); + let serv = ast_to_type(&env, &serv.unwrap()).unwrap(); let method = &env.get_method(&serv, "manage_neuron").unwrap(); let arg_tys = method .args diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 1bcd1cb2e..418e11f3c 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -119,7 +119,14 @@ pub fn map_type(env: &Env, t: &IDLType) -> Result { Ok(TypeInner::Service(ms).into()) } IDLType::KnotT(id) => Ok(TypeInner::Knot(id.clone()).into()), - IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + IDLType::ClassT(args, serv) => { + let serv = map_type(env, serv)?; + let args = args + .iter() + .map(|arg| map_arg(env, arg)) + .collect::>>()?; + Ok(TypeInner::Class(args, serv).into()) + } } } From 2bc94c66ec789c828bcd0a563ed788da97e9ee30 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Sun, 22 Jun 2025 15:55:00 +0200 Subject: [PATCH 23/34] chore: update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ae547b4..ea5d8bb3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ - `Binding` - `IDLProg` - `IDLInitArgs` + + `IDLType::KnotT` is now a valid type. + + The `IDLEnv` struct has been added to support the parsing and bindings generation, to avoid using the `TypeEnv`. + + The IDL types can now be converted to `Type` and vice versa, using the `From` trait. ### candid_parser @@ -43,6 +46,12 @@ You must now use the `parse_idl_prog`, `parse_idl_init_args`, `parse_idl_type` and `parse_idl_types` functions to parse these types, respectively. + `pretty_parse` doesn't work anymore with the `IDLProg` and `IDLTypes` types. Use `pretty_parse_idl_prog` and `pretty_parse_idl_types` instead. + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The bindings generators now use the IDL env and types. + + `check_file` and `pretty_check_file` now return a `(TypeEnv, IDLEnv, Option)` tuple, instead of `(TypeEnv, Option)`. Same for the `CandidSource.load` method. + + `check_prog` now returns a `Result>` instead of `Result>`. + + `typing::check_init_args` now accepts the additional `&mut IDLEnv` parameter. + + `utils::get_metadata` now accepts only an `&IDLEnv` parameter. + + The `chase_type`, `chase_actor`, `chase_def_use`, `chase_types` and `infer_rec` functions of the `bindings::analysis` module now accept an IDL env and types as parameters. * Non-breaking changes: + Supports parsing the arguments' names for `func` and `service` (init args). From eb9fad4392e236bd860858f42ab72c93620310c8 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Mon, 23 Jun 2025 15:49:30 +0200 Subject: [PATCH 24/34] refactor: restore `TypeEnv` in the `typing` module --- rust/candid/src/pretty/candid.rs | 11 +- rust/candid/src/types/internal.rs | 98 +++--- rust/candid/src/types/syntax.rs | 174 +++------- rust/candid_derive/src/func.rs | 28 +- rust/candid_parser/src/bindings/analysis.rs | 55 ++-- rust/candid_parser/src/bindings/javascript.rs | 67 ++-- rust/candid_parser/src/bindings/motoko.rs | 10 +- rust/candid_parser/src/bindings/rust.rs | 67 ++-- rust/candid_parser/src/bindings/typescript.rs | 68 ++-- rust/candid_parser/src/configs.rs | 13 +- rust/candid_parser/src/random.rs | 32 +- rust/candid_parser/src/typing.rs | 298 ++++++------------ rust/candid_parser/src/utils.rs | 65 ++-- .../tests/assets/ok/undefine.fail | 2 +- rust/candid_parser/tests/parse_type.rs | 19 +- rust/candid_parser/tests/value.rs | 2 - 16 files changed, 396 insertions(+), 613 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index b59f9c415..62cc4f60e 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -1,6 +1,6 @@ use crate::pretty::utils::*; use crate::types::{ - syntax::{Binding, FuncType, IDLArgType, IDLEnv, IDLType, PrimType, TypeField}, + syntax::{Binding, FuncType, IDLArgType, IDLMergedProg, IDLType, PrimType, TypeField}, FuncMode, Label, }; use pretty::RcDoc; @@ -125,7 +125,6 @@ pub fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } - KnotT(ref id) => RcDoc::text(format!("{id}")), } } @@ -203,8 +202,8 @@ fn pp_service(serv: &[Binding]) -> RcDoc { enclose_space("{", doc, "}") } -fn pp_defs(env: &IDLEnv) -> RcDoc { - lines(env.get_bindings().iter().map(|(id, typ)| { +fn pp_defs(env: &IDLMergedProg) -> RcDoc { + lines(env.get_types().iter().map(|(id, typ)| { kwd("type") .append(ident(id)) .append(kwd("=")) @@ -221,10 +220,10 @@ fn pp_actor(ty: &IDLType) -> RcDoc { } } -pub fn pp_init_args<'a>(env: &'a IDLEnv, args: &'a [IDLArgType]) -> RcDoc<'a> { +pub fn pp_init_args<'a>(env: &'a IDLMergedProg, args: &'a [IDLArgType]) -> RcDoc<'a> { pp_defs(env).append(pp_args(args)) } -pub fn compile(env: &IDLEnv) -> String { +pub fn compile(env: &IDLMergedProg) -> String { match &env.actor { None => pp_defs(env).pretty(LINE_WIDTH).to_string(), Some(actor) => { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 8d30d641f..dcb5531d7 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -1,6 +1,5 @@ use super::CandidType; use crate::idl_hash; -use crate::types::syntax::{Binding, FuncType, IDLArgType, IDLType, TypeField}; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BTreeMap; @@ -71,9 +70,9 @@ impl TypeName { } } -/// Used for `candid_derive::export_service` to generate `IDLEnv` from `IDLType`. +/// Used for `candid_derive::export_service` to generate `TypeEnv` from `Type`. /// -/// It performs a global rewriting of `IDLType` to resolve: +/// It performs a global rewriting of `Type` to resolve: /// * Duplicate type names in different modules/namespaces. /// * Give different names to instantiated polymorphic types. /// * Find the type name of a recursive node `Knot(TypeId)` and convert to `Var` node. @@ -84,108 +83,99 @@ impl TypeName { /// * Unless we do equivalence checking, recursive types can be unrolled and assigned to multiple names. #[derive(Default)] pub struct TypeContainer { - pub idl_env: crate::types::syntax::IDLEnv, + pub env: crate::TypeEnv, } impl TypeContainer { pub fn new() -> Self { TypeContainer { - idl_env: crate::types::syntax::IDLEnv::new(), + env: crate::TypeEnv::new(), } } - pub fn add(&mut self) -> IDLType { + pub fn add(&mut self) -> Type { let t = T::ty(); - self.go(&t.into()) - } - fn go(&mut self, t: &IDLType) -> IDLType { - match t { - IDLType::OptT(t) => IDLType::OptT(Box::new(self.go(t))), - IDLType::VecT(t) => IDLType::VecT(Box::new(self.go(t))), - IDLType::RecordT(fs) => { - let res = IDLType::RecordT( + self.go(&t) + } + fn go(&mut self, t: &Type) -> Type { + 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( fs.iter() - .map(|TypeField { label, typ }| TypeField { - label: label.clone(), - typ: self.go(typ), + .map(|Field { id, ty }| Field { + id: id.clone(), + ty: self.go(ty), }) .collect(), - ); + ) + .into(); if t.is_tuple() { return res; } - let id = ID.with(|n| n.borrow().get(&t.clone().into()).cloned()); + let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - self.idl_env.insert_binding(Binding { - id: id.to_string(), - typ: res, - }); - IDLType::VarT(id.to_string()) + self.env.0.insert(id.to_string(), res); + TypeInner::Var(id.to_string()) } else { // if the type is part of an enum, the id won't be recorded. // we want to inline the type in this case. - res + return res; } } - IDLType::VariantT(fs) => { - let res = IDLType::VariantT( + TypeInner::Variant(fs) => { + let res: Type = TypeInner::Variant( fs.iter() - .map(|TypeField { label, typ }| TypeField { - label: label.clone(), - typ: self.go(typ), + .map(|Field { id, ty }| Field { + id: id.clone(), + ty: self.go(ty), }) .collect(), - ); - let id = ID.with(|n| n.borrow().get(&t.clone().into()).cloned()); + ) + .into(); + let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - self.idl_env.insert_binding(Binding { - id: id.to_string(), - typ: res, - }); - IDLType::VarT(id.to_string()) + self.env.0.insert(id.to_string(), res); + TypeInner::Var(id.to_string()) } else { - res + return res; } } - IDLType::KnotT(id) => { + TypeInner::Knot(id) => { let name = id.to_string(); let ty = ENV.with(|e| e.borrow().get(id).unwrap().clone()); - self.idl_env.insert_binding(Binding { - id: name.clone(), - typ: ty.into(), - }); - IDLType::VarT(name) + self.env.0.insert(id.to_string(), ty); + TypeInner::Var(name) } - IDLType::FuncT(func) => IDLType::FuncT(FuncType { + TypeInner::Func(func) => TypeInner::Func(Function { modes: func.modes.clone(), args: func .args .iter() - .map(|arg| IDLArgType { + .map(|arg| ArgType { name: arg.name.clone(), typ: self.go(&arg.typ), }) .collect(), rets: func.rets.iter().map(|arg| self.go(arg)).collect(), }), - IDLType::ServT(serv) => IDLType::ServT( + TypeInner::Service(serv) => TypeInner::Service( serv.iter() - .map(|binding| Binding { - id: binding.id.clone(), - typ: self.go(&binding.typ), - }) + .map(|(id, t)| (id.clone(), self.go(t))) .collect(), ), - IDLType::ClassT(inits, ref ty) => IDLType::ClassT( + TypeInner::Class(inits, ref ty) => TypeInner::Class( inits .iter() - .map(|t| IDLArgType { + .map(|t| ArgType { name: t.name.clone(), typ: self.go(&t.typ), }) .collect(), - Box::new(self.go(ty)), + self.go(ty), ), t => t.clone(), } + .into() } } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index ad78a9e28..2484135e9 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -1,9 +1,6 @@ -use std::{collections::BTreeMap, fmt}; +use std::fmt; -use crate::{ - types::{ArgType, Field, FuncMode, Function, Label, Type, TypeId, TypeInner}, - TypeEnv, -}; +use crate::types::{ArgType, Field, FuncMode, Function, Label, Type, TypeInner}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum IDLType { @@ -17,8 +14,6 @@ pub enum IDLType { ServT(Vec), ClassT(Vec, Box), PrincipalT, - /// Used for Rust recursive types. - KnotT(TypeId), } impl IDLType { @@ -84,7 +79,6 @@ impl From for Type { TypeInner::Class(args.into_iter().map(|t| t.into()).collect(), (*t).into()) } IDLType::PrincipalT => TypeInner::Principal, - IDLType::KnotT(id) => TypeInner::Knot(id), } .into() } @@ -134,8 +128,7 @@ impl From for IDLType { Box::new(t.clone().into()), ), TypeInner::Principal => IDLType::PrincipalT, - TypeInner::Knot(id) => IDLType::KnotT(id.clone()), - TypeInner::Unknown | TypeInner::Future => { + TypeInner::Knot(_) | TypeInner::Unknown | TypeInner::Future => { panic!("Unknown type: {:?}", t) } } @@ -279,7 +272,7 @@ impl From for TypeField { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Dec { TypD(Binding), ImportType(String), @@ -292,7 +285,7 @@ pub struct Binding { pub typ: IDLType, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct IDLProg { pub decs: Vec, pub actor: Option, @@ -304,154 +297,94 @@ pub struct IDLInitArgs { pub args: Vec, } +/// Generated from concatenating different `IDLProg`s. #[derive(Debug, Default)] -pub struct IDLEnv { - types: BTreeMap>, - types_bindings_ids: Vec, +pub struct IDLMergedProg { + types: Vec, pub actor: Option, } -impl From<&IDLProg> for IDLEnv { - fn from(prog: &IDLProg) -> Self { - let mut env = Self::new(); - - for dec in prog.decs.iter() { - if let Dec::TypD(binding) = dec { - env.insert_binding(binding.clone()); - } - } - - env.set_actor(prog.actor.clone()); - env +impl From for IDLMergedProg { + fn from(other_prog: IDLProg) -> Self { + let mut merged_prog = Self::default(); + merged_prog.add_decs(&other_prog.decs); + merged_prog.set_actor(other_prog.actor); + merged_prog } } -impl From> for IDLEnv { +impl From> for IDLMergedProg { fn from(bindings: Vec) -> Self { - let mut env = Self::default(); - for binding in bindings { - env.insert_binding(binding.clone()); + Self { + types: bindings, + actor: None, } - env } } -impl From for IDLEnv { - fn from(env: TypeEnv) -> Self { - let mut idl_env = Self::default(); - for (id, t) in env.0 { - idl_env.insert_binding(Binding { id, typ: t.into() }); - } - idl_env - } -} - -impl From for TypeEnv { - fn from(env: IDLEnv) -> Self { - let mut type_env = Self::default(); - for (id, t) in env - .types - .iter() - .filter_map(|(id, t)| t.as_ref().map(|t| (id, t))) - { - type_env.0.insert(id.to_string(), t.clone().into()); - } - type_env - } -} - -impl IDLEnv { +impl IDLMergedProg { pub fn new() -> Self { Self::default() } - pub fn insert_binding(&mut self, binding: Binding) { - let duplicate = self - .types - .insert(binding.id.clone(), Some(binding.typ.clone())); - if duplicate.is_none() { - self.types_bindings_ids.push(binding.id.clone()); - } - } - - pub fn insert_unknown(&mut self, id: String) { - let duplicate = self.types.insert(id.clone(), None); - if duplicate.is_none() { - self.types_bindings_ids.push(id); - } - } - - pub fn types_ids(&self) -> Vec<&str> { - self.types_bindings_ids + pub fn add_decs(&mut self, decs: &[Dec]) { + let types: Vec = decs .iter() - .map(|id| id.as_str()) - .collect() - } - - pub fn set_actor(&mut self, actor: Option) { - self.actor = actor; + .filter_map(|dec| match dec { + Dec::TypD(binding) => Some(binding.clone()), + Dec::ImportType(_) | Dec::ImportServ(_) => None, + }) + .collect(); + self.types.extend(types); } - pub fn find_binding<'a>(&'a self, id: &'a str) -> Result<(&'a str, &'a IDLType), String> { - self.find_type(id).map(|t| (id, t)) + pub fn set_actor(&mut self, other: Option) { + self.actor = other; } pub fn find_type(&self, id: &str) -> Result<&IDLType, String> { self.types - .get(id) + .iter() + .find_map(|t| if t.id == id { Some(&t.typ) } else { None }) .ok_or(format!("Type identifier not found: {id}")) - .and_then(|t| { - t.as_ref() - .ok_or(format!("Type identifier is unknown: {id}")) - }) } - pub fn assert_has_type(&self, id: &str) -> Result<(), String> { - if self.types.contains_key(id) { - Ok(()) - } else { - Err(format!("Type identifier not found: {id}")) + pub fn rec_find_type(&self, name: &str) -> Result<&IDLType, String> { + let t = self.find_type(name)?; + match t { + IDLType::VarT(id) => self.rec_find_type(id), + _ => Ok(t), } } - pub fn get_types(&self) -> Vec<&IDLType> { - self.types_bindings_ids - .iter() - .map(|id| self.find_type(id).unwrap()) - .collect() + pub fn get_types(&self) -> Vec<(&str, &IDLType)> { + self.types.iter().map(|t| (t.id.as_str(), &t.typ)).collect() } - pub fn get_bindings(&self) -> Vec<(&str, &IDLType)> { - self.types_bindings_ids - .iter() - .map(|id| self.find_binding(id).unwrap()) - .collect() + pub fn get_bindings(&self) -> Vec { + self.types.clone() } - pub fn rec_find_type(&self, name: &str) -> Result<&IDLType, String> { - let t = self.find_type(name)?; - match t { - IDLType::VarT(id) => self.rec_find_type(id), - _ => Ok(t), - } + pub fn insert_binding(&mut self, binding: Binding) { + self.types.push(binding); + } + + pub fn types_ids(&self) -> Vec<&str> { + self.types.iter().map(|t| t.id.as_str()).collect() } pub fn trace_type(&self, t: &IDLType) -> Result { match t { IDLType::VarT(id) => self.trace_type(self.find_type(id)?), - IDLType::KnotT(id) => { - let t = crate::types::internal::find_type(id).unwrap(); - self.trace_type(&t.into()) - } _ => Ok(t.clone()), } } - pub fn as_service<'a>(&'a self, t: &'a IDLType) -> Result<&'a Vec, String> { + pub fn service_methods<'a>(&'a self, t: &'a IDLType) -> Result<&'a Vec, String> { match t { IDLType::ServT(methods) => Ok(methods), - IDLType::VarT(id) => self.as_service(self.find_type(id)?), - IDLType::ClassT(_, t) => self.as_service(t), + IDLType::VarT(id) => self.service_methods(self.find_type(id)?), + IDLType::ClassT(_, t) => self.service_methods(t), _ => Err(format!("not a service type: {t}")), } } @@ -463,13 +396,4 @@ impl IDLEnv { _ => Err(format!("not a function type: {:?}", t)), } } - - pub fn get_method<'a>(&'a self, t: &'a IDLType, id: &'a str) -> Result<&'a FuncType, String> { - for binding in self.as_service(t)? { - if binding.id == id { - return self.as_func(&binding.typ); - } - } - Err(format!("cannot find method {id}")) - } } diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 4bd8a4533..7c886563e 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -84,7 +84,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { .map(|t| generate_arg(quote! { init_args }, t)) .collect::>(); quote! { - let mut init_args: Vec = Vec::new(); + let mut init_args: Vec = Vec::new(); #(#args)* } }); @@ -111,30 +111,27 @@ pub(crate) fn export_service(path: Option) -> TokenStream { }; quote! { { - let mut args: Vec = Vec::new(); + let mut args: Vec = Vec::new(); #(#args)* - let mut rets: Vec = Vec::new(); + let mut rets: Vec = Vec::new(); #(#rets)* - let func = FuncType { args, rets, modes: #modes }; - service.push(Binding { - id: #name.to_string(), - typ: IDLType::FuncT(func), - }); + let func = Function { args, rets, modes: #modes }; + service.push((#name.to_string(), TypeInner::Func(func).into())); } } }); let service = quote! { - use #candid::types::syntax::{IDLArgType, FuncType, IDLType, Binding}; - let mut service = Vec::::new(); + use #candid::types::{CandidType, Function, Type, ArgType, TypeInner}; + let mut service = Vec::<(String, Type)>::new(); let mut env = #candid::types::internal::TypeContainer::new(); #(#gen_tys)* - service.sort_unstable_by_key(|b| b.id.clone()); - let ty = IDLType::ServT(service); + service.sort_unstable_by_key(|(name, _)| name.clone()); + let ty = TypeInner::Service(service).into(); }; let actor = if let Some(init) = init { quote! { #init - let actor = Some(IDLType::ClassT(init_args, Box::new(ty))); + let actor = Some(TypeInner::Class(init_args, ty).into()); } } else { quote! { let actor = Some(ty); } @@ -143,8 +140,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { fn __export_service() -> String { #service #actor - env.idl_env.set_actor(actor); - let result = #candid::pretty::candid::compile(&env.idl_env); + let result = #candid::pretty::candid::compile(&env.env, &actor); format!("{}", result) } }; @@ -163,7 +159,7 @@ fn generate_arg(name: TokenStream, (arg_name, ty): &(Option, String)) -> .unwrap_or(quote! { None }); let ty = syn::parse_str::(ty.as_str()).unwrap(); quote! { - #name.push(IDLArgType { name: #arg_name, typ: env.add::<#ty>() }); + #name.push(ArgType { name: #arg_name, typ: env.add::<#ty>() }); } } diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index 6c24759ad..b8c84038c 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -1,6 +1,6 @@ use crate::{Error, Result}; use candid::types::{ - syntax::{IDLEnv, IDLType}, + syntax::{IDLMergedProg, IDLType}, Type, TypeEnv, TypeInner, }; use std::collections::{BTreeMap, BTreeSet}; @@ -35,40 +35,40 @@ pub fn project_methods( pub fn chase_type<'a>( seen: &mut BTreeSet<&'a str>, res: &mut Vec<&'a str>, - env: &'a IDLEnv, + prog: &'a IDLMergedProg, t: &'a IDLType, ) -> Result<()> { use IDLType::*; match t { VarT(id) => { if seen.insert(id) { - let t = env.find_type(id).map_err(Error::msg)?; - chase_type(seen, res, env, t)?; + let t = prog.find_type(id).map_err(Error::msg)?; + chase_type(seen, res, prog, t)?; res.push(id); } } - OptT(ty) | VecT(ty) => chase_type(seen, res, env, ty)?, + OptT(ty) | VecT(ty) => chase_type(seen, res, prog, ty)?, RecordT(fs) | VariantT(fs) => { for f in fs.iter() { - chase_type(seen, res, env, &f.typ)?; + chase_type(seen, res, prog, &f.typ)?; } } FuncT(f) => { let args = f.args.iter().map(|arg| &arg.typ); for ty in args.clone().chain(f.rets.iter()) { - chase_type(seen, res, env, ty)?; + chase_type(seen, res, prog, ty)?; } } ServT(bindings) => { for binding in bindings.iter() { - chase_type(seen, res, env, &binding.typ)?; + chase_type(seen, res, prog, &binding.typ)?; } } ClassT(args, t) => { for arg in args.iter() { - chase_type(seen, res, env, &arg.typ)?; + chase_type(seen, res, prog, &arg.typ)?; } - chase_type(seen, res, env, t)?; + chase_type(seen, res, prog, t)?; } _ => (), } @@ -77,21 +77,21 @@ pub fn chase_type<'a>( /// Gather type definitions mentioned in actor, return the non-recursive type names in topological order. /// Recursive types can appear in any order. -pub fn chase_actor<'a>(env: &'a IDLEnv, actor: &'a IDLType) -> Result> { +pub fn chase_actor<'a>(prog: &'a IDLMergedProg, actor: &'a IDLType) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); - chase_type(&mut seen, &mut res, env, actor)?; + chase_type(&mut seen, &mut res, prog, actor)?; Ok(res) } /// Given an actor, return a map from variable names to the (methods, arg) that use them. -pub fn chase_def_use(env: &IDLEnv) -> Result>> { +pub fn chase_def_use(prog: &IDLMergedProg) -> Result>> { let mut res = BTreeMap::new(); - let actor = env.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; - let actor = env.trace_type(actor).map_err(Error::msg)?; + let actor = prog.actor.as_ref().ok_or_else(|| Error::msg("no actor"))?; + let actor = prog.trace_type(actor).map_err(Error::msg)?; if let IDLType::ClassT(args, _) = &actor { for (i, arg) in args.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; + chase_type(&mut BTreeSet::new(), &mut used, prog, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -99,11 +99,11 @@ pub fn chase_def_use(env: &IDLEnv) -> Result>> { } } } - for binding in env.as_service(&actor).map_err(Error::msg)? { - let func = env.as_func(&binding.typ).map_err(Error::msg)?; + for binding in prog.service_methods(&actor).map_err(Error::msg)? { + let func = prog.as_func(&binding.typ).map_err(Error::msg)?; for (i, arg) in func.args.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, &arg.typ)?; + chase_type(&mut BTreeSet::new(), &mut used, prog, &arg.typ)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -112,7 +112,7 @@ pub fn chase_def_use(env: &IDLEnv) -> Result>> { } for (i, arg) in func.rets.iter().enumerate() { let mut used = Vec::new(); - chase_type(&mut BTreeSet::new(), &mut used, env, arg)?; + chase_type(&mut BTreeSet::new(), &mut used, prog, arg)?; for var in used { res.entry(var.to_string()) .or_insert_with(Vec::new) @@ -123,23 +123,26 @@ pub fn chase_def_use(env: &IDLEnv) -> Result>> { Ok(res) } -pub fn chase_types<'a>(env: &'a IDLEnv, tys: &'a [IDLType]) -> Result> { +pub fn chase_types<'a>(prog: &'a IDLMergedProg, tys: &'a [IDLType]) -> Result> { let mut seen = BTreeSet::new(); let mut res = Vec::new(); for t in tys.iter() { - chase_type(&mut seen, &mut res, env, t)?; + chase_type(&mut seen, &mut res, prog, t)?; } Ok(res) } /// Given a `def_list` produced by the `chase_actor` function, infer which types are recursive -pub fn infer_rec<'a>(_env: &'a IDLEnv, def_list: &'a [&'a str]) -> Result> { +pub fn infer_rec<'a>( + _prog: &'a IDLMergedProg, + def_list: &'a [&'a str], +) -> Result> { let mut seen = BTreeSet::new(); let mut res = BTreeSet::new(); fn go<'a>( seen: &mut BTreeSet<&'a str>, res: &mut BTreeSet<&'a str>, - _env: &'a IDLEnv, + _env: &'a IDLMergedProg, t: &'a IDLType, ) -> Result<()> { use IDLType::*; @@ -177,8 +180,8 @@ pub fn infer_rec<'a>(_env: &'a IDLEnv, def_list: &'a [&'a str]) -> Result RcDoc { VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), FuncT(ref func) => str("IDL.Func").append(pp_function(func)), ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), - ClassT(_, _) | KnotT(_) => unreachable!(), + ClassT(_, _) => unreachable!(), } } @@ -199,7 +199,11 @@ fn pp_service(serv: &[Binding]) -> RcDoc { enclose_space("({", doc, "})") } -fn pp_defs<'a>(env: &'a IDLEnv, def_list: &'a [&'a str], recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { +fn pp_defs<'a>( + env: &'a IDLMergedProg, + def_list: &'a [&'a str], + recs: &'a BTreeSet<&'a str>, +) -> RcDoc<'a> { let recs_doc = lines( recs.iter() .map(|id| kwd("const").append(ident(id)).append(" = IDL.Rec();")), @@ -236,18 +240,18 @@ fn pp_actor<'a>(ty: &'a IDLType, recs: &'a BTreeSet<&'a str>) -> RcDoc<'a> { } } -pub fn compile(env: &IDLEnv) -> String { - match &env.actor { +pub fn compile(prog: &IDLMergedProg) -> String { + match &prog.actor { None => { - let def_list: Vec<_> = env.types_ids(); - let recs = infer_rec(env, &def_list).unwrap(); - let doc = pp_defs(env, &def_list, &recs); + let def_list: Vec<_> = prog.types_ids(); + let recs = infer_rec(prog, &def_list).unwrap(); + let doc = pp_defs(prog, &def_list, &recs); doc.pretty(LINE_WIDTH).to_string() } Some(actor) => { - let def_list = chase_actor(env, actor).unwrap(); - let recs = infer_rec(env, &def_list).unwrap(); - let defs = pp_defs(env, &def_list, &recs); + let def_list = chase_actor(prog, actor).unwrap(); + let recs = infer_rec(prog, &def_list).unwrap(); + let defs = pp_defs(prog, &def_list, &recs); let init = if let IDLType::ClassT(ref args, _) = actor { args.iter().map(|arg| arg.typ.clone()).collect::>() } else { @@ -259,9 +263,9 @@ pub fn compile(env: &IDLEnv) -> String { let doc = str("export const idlFactory = ({ IDL }) => ") .append(enclose_space("{", body, "};")); // export init args - let init_defs = chase_types(env, init).unwrap(); - let init_recs = infer_rec(env, &init_defs).unwrap(); - let init_defs_doc = pp_defs(env, &init_defs, &init_recs); + let init_defs = chase_types(prog, init).unwrap(); + let init_recs = infer_rec(prog, &init_defs).unwrap(); + let init_defs_doc = pp_defs(prog, &init_defs, &init_recs); let init_doc = kwd("return").append(pp_rets(init)).append(";"); let init_doc = init_defs_doc.append(init_doc); let init_doc = @@ -368,10 +372,10 @@ pub mod value { pub mod test { use super::value; - use crate::test::{to_idl_types, HostAssert, HostTest, Test}; + use crate::test::{HostAssert, HostTest, Test}; use candid::pretty::utils::*; - use candid::types::syntax::{IDLEnv, IDLType}; - use candid::types::{syntax::IDLProg, TypeEnv}; + use candid::types::syntax::{IDLMergedProg, IDLProg}; + use candid::TypeEnv; use pretty::RcDoc; fn pp_hex(bytes: &[u8]) -> RcDoc { @@ -379,17 +383,17 @@ pub mod test { .append(RcDoc::as_string(hex::encode(bytes))) .append("', 'hex')") } - fn pp_encode<'a>(args: &'a candid::IDLArgs, tys: &'a [IDLType]) -> RcDoc<'a> { + fn pp_encode<'a>(args: &'a candid::IDLArgs, tys: &'a [candid::types::Type]) -> RcDoc<'a> { let vals = value::pp_args(args); - let tys = super::pp_rets(tys); + let tys = super::pp_rets(&[]); let items = [tys, vals]; let params = concat(items.iter().cloned(), ","); str("IDL.encode").append(enclose("(", params, ")")) } - fn pp_decode<'a>(bytes: &'a [u8], tys: &'a [IDLType]) -> RcDoc<'a> { + fn pp_decode<'a>(bytes: &'a [u8], tys: &'a [candid::types::Type]) -> RcDoc<'a> { let hex = pp_hex(bytes); - let tys = super::pp_rets(tys); + let tys = super::pp_rets(&[]); let items = [tys, hex]; let params = concat(items.iter().cloned(), ","); str("IDL.decode").append(enclose("(", params, ")")) @@ -408,29 +412,25 @@ import { Principal } from './principal'; decs: test.defs, actor: None, }; - let idl_env = IDLEnv::from(&idl_prog); crate::check_prog(&mut env, &idl_prog).unwrap(); - res += &super::compile(&idl_env); + let idl_merged_prog = IDLMergedProg::from(idl_prog); + res += &super::compile(&idl_merged_prog); for (i, assert) in test.asserts.iter().enumerate() { let mut types = Vec::new(); for ty in assert.typ.iter() { - types.push((ty.clone(), crate::typing::ast_to_type(&env, ty).unwrap())); + types.push(crate::typing::ast_to_type(&env, ty).unwrap()); } - let host = HostTest::from_assert(assert, &env, &types); + let host = HostTest::from_assert(assert, &env, &[]); let mut expects = Vec::new(); for cmd in host.asserts.iter() { use HostAssert::*; let test_func = match cmd { Encode(args, tys, _, _) | NotEncode(args, tys) => { - let idl_tys = to_idl_types(tys); - let items = [super::pp_rets(&[]), pp_encode(args, &[])]; + let items = []; let params = concat(items.iter().cloned(), ","); str("IDL.decode").append(enclose("(", params, ")")) } - Decode(bytes, tys, _, _) | NotDecode(bytes, tys) => { - let idl_tys = to_idl_types(tys); - pp_decode(bytes, &[]) - } + Decode(bytes, tys, _, _) | NotDecode(bytes, tys) => RcDoc::nil(), }; let (test_func, predicate) = match cmd { Encode(_, _, true, _) | Decode(_, _, true, _) => (test_func, str(".toEqual")), @@ -442,10 +442,7 @@ import { Principal } from './principal'; } }; let expected = match cmd { - Encode(_, tys, _, bytes) => { - let idl_tys = to_idl_types(tys); - pp_decode(bytes, &[]) - } + Encode(_, tys, _, bytes) => RcDoc::nil(), Decode(_, _, _, vals) => value::pp_args(vals), NotEncode(_, _) | NotDecode(_, _) => RcDoc::nil(), }; diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 6fd69f47b..a41dc387e 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -3,8 +3,9 @@ use candid::pretty::candid::is_valid_as_id; use candid::pretty::utils::*; +use candid::types::syntax::IDLMergedProg; use candid::types::{ - syntax::{Binding, FuncType, IDLArgType, IDLEnv, IDLType, PrimType, TypeField}, + syntax::{Binding, FuncType, IDLArgType, IDLType, PrimType, TypeField}, FuncMode, Label, }; use pretty::RcDoc; @@ -147,7 +148,6 @@ fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } - KnotT(_) => unreachable!(), } } @@ -270,12 +270,12 @@ fn pp_actor(ty: &IDLType) -> RcDoc { } } -pub fn compile(env: &IDLEnv) -> String { +pub fn compile(prog: &IDLMergedProg) -> String { let header = r#"// This is a generated Motoko binding. // Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. "#; - let bindings = env.get_bindings(); - let doc = match &env.actor { + let bindings = prog.get_types(); + let doc = match &prog.actor { None => pp_defs(&bindings), Some(actor) => { let defs = pp_defs(&bindings); diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 48e10ceac..02e627d97 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -3,11 +3,11 @@ use crate::{ configs::{ConfigState, ConfigTree, Configs, Context, StateElem}, Deserialize, }; -use candid::pretty::utils::*; use candid::types::{ - syntax::{Binding, FuncType, IDLArgType, IDLEnv, IDLType, PrimType, TypeField}, + syntax::{Binding, FuncType, IDLArgType, IDLType, PrimType, TypeField}, Label, }; +use candid::{pretty::utils::*, types::syntax::IDLMergedProg}; use convert_case::{Case, Casing}; use pretty::RcDoc; use serde::Serialize; @@ -170,21 +170,17 @@ impl<'a> State<'a> { if self.tests.contains_key(use_type) { return; } - let def_list = chase_actor(self.state.env, src).unwrap(); - let env = IDLEnv::from( - self.state - .env - .get_bindings() - .into_iter() - .filter(|(id, _)| def_list.contains(id)) - .map(|(id, typ)| Binding { - id: id.to_string(), - typ: typ.clone(), - }) - .collect::>(), - ); + let def_list = chase_actor(self.state.prog, src).unwrap(); + let filtered_bindings = self + .state + .prog + .get_bindings() + .into_iter() + .filter(|b| def_list.contains(&b.id.as_str())) + .collect::>(); + let prog = IDLMergedProg::from(filtered_bindings); let src = candid::pretty::candid::pp_init_args( - &env, + &prog, &[IDLArgType { name: None, typ: src.clone(), @@ -287,7 +283,7 @@ fn test_{test_name}() {{ } FuncT(_) => unreachable!(), // not possible after rewriting ServT(_) => unreachable!(), // not possible after rewriting - ClassT(_, _) | KnotT(_) => unreachable!(), + ClassT(_, _) => unreachable!(), } }; self.state.pop_state(old, elem); @@ -431,7 +427,7 @@ fn test_{test_name}() {{ self.state.pop_state(old, StateElem::Label(id)); continue; } - let ty = self.state.env.find_type(id).unwrap(); + let ty = self.state.prog.find_type(id).unwrap(); let name = self .state .config @@ -650,7 +646,7 @@ fn test_{test_name}() {{ res } fn pp_actor(&mut self, actor: &IDLType) -> (Vec, Option>) { - let actor = self.state.env.trace_type(actor).unwrap(); + let actor = self.state.prog.trace_type(actor).unwrap(); let init = if let IDLType::ClassT(args, _) = &actor { let old = self.state.push_state(&StateElem::Label("init")); let args: Vec<_> = args @@ -676,10 +672,10 @@ fn test_{test_name}() {{ } else { None }; - let serv = self.state.env.as_service(&actor).unwrap(); + let serv = self.state.prog.service_methods(&actor).unwrap(); let mut res = Vec::new(); for binding in serv.iter() { - let func = self.state.env.as_func(&binding.typ).unwrap(); + let func = self.state.prog.as_func(&binding.typ).unwrap(); res.push(self.pp_function(&binding.id, func)); } (res, init) @@ -700,9 +696,9 @@ pub struct Method { pub rets: Vec, pub mode: String, } -pub fn emit_bindgen(tree: &Config, env: &IDLEnv) -> (Output, Vec) { +pub fn emit_bindgen(tree: &Config, prog: &IDLMergedProg) -> (Output, Vec) { let mut state = NominalState { - state: crate::configs::State::new(&tree.0, env), + state: crate::configs::State::new(&tree.0, prog), }; let env = state.nominalize_all(); let old_stats = state.state.stats.clone(); @@ -778,12 +774,16 @@ impl Default for ExternalConfig { ) } } -pub fn compile(tree: &Config, env: &IDLEnv, mut external: ExternalConfig) -> (String, Vec) { +pub fn compile( + tree: &Config, + prog: &IDLMergedProg, + mut external: ExternalConfig, +) -> (String, Vec) { let source = match external.0.get("target").map(|s| s.as_str()) { Some("canister_call") | None => Cow::Borrowed(include_str!("rust_call.hbs")), Some("agent") => Cow::Borrowed(include_str!("rust_agent.hbs")), Some("stub") => { - let metadata = crate::utils::get_metadata(env); + let metadata = crate::utils::get_metadata(prog); if let Some(metadata) = metadata { external.0.insert("metadata".to_string(), metadata); } @@ -798,7 +798,7 @@ pub fn compile(tree: &Config, env: &IDLEnv, mut external: ExternalConfig) -> (St } _ => unimplemented!(), }; - let (output, unused) = emit_bindgen(tree, env); + let (output, unused) = emit_bindgen(tree, prog); (output_handlebar(output, external, &source), unused) } @@ -833,7 +833,12 @@ struct NominalState<'a> { } impl NominalState<'_> { // Convert structural typing to nominal typing to fit Rust's type system - fn nominalize(&mut self, env: &mut IDLEnv, path: &mut Vec, t: &IDLType) -> IDLType { + fn nominalize( + &mut self, + env: &mut IDLMergedProg, + path: &mut Vec, + t: &IDLType, + ) -> IDLType { let elem = StateElem::Type(t); let old = if matches!(t, IDLType::FuncT(_)) { // strictly speaking, we want to avoid func label from the main service. But this is probably good enough. @@ -1068,9 +1073,9 @@ impl NominalState<'_> { res } - fn nominalize_all(&mut self) -> IDLEnv { - let mut res = IDLEnv::new(); - for (id, typ) in self.state.env.get_bindings() { + fn nominalize_all(&mut self) -> IDLMergedProg { + let mut res = IDLMergedProg::new(); + for (id, typ) in self.state.prog.get_types() { let elem = StateElem::Label(id); let old = self.state.push_state(&elem); let ty = self.nominalize(&mut res, &mut vec![TypePath::Id(id.to_string())], typ); @@ -1082,7 +1087,7 @@ impl NominalState<'_> { } let actor = self .state - .env + .prog .actor .as_ref() .map(|ty| self.nominalize(&mut res, &mut vec![], ty)); diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 67f3e1fa6..75dad62f5 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -1,7 +1,7 @@ use super::javascript::{ident, is_tuple}; use candid::pretty::utils::*; use candid::types::{ - syntax::{Binding, FuncType, IDLEnv, IDLType, PrimType, TypeField}, + syntax::{Binding, FuncType, IDLMergedProg, IDLType, PrimType, TypeField}, Label, }; use pretty::RcDoc; @@ -29,15 +29,15 @@ fn pp_prim_ty(ty: &PrimType) -> RcDoc { } } -fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { +fn pp_ty<'a>(prog: &'a IDLMergedProg, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { use IDLType::*; match ty { PrimT(ref ty) => pp_prim_ty(ty), VarT(ref id) => { if is_ref { - let ty = env.rec_find_type(id).unwrap(); + let ty = prog.rec_find_type(id).unwrap(); if matches!(ty, ServT(_) | FuncT(_)) { - pp_ty(env, ty, false) + pp_ty(prog, ty, false) } else { ident(id) } @@ -46,11 +46,11 @@ fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { } } PrincipalT => str("Principal"), - OptT(ref t) => str("[] | ").append(enclose("[", pp_ty(env, t, is_ref), "]")), + OptT(ref t) => str("[] | ").append(enclose("[", pp_ty(prog, t, is_ref), "]")), VecT(ref t) => { let ty = match t.as_ref() { VarT(ref id) => { - let ty = env.rec_find_type(id).unwrap(); + let ty = prog.rec_find_type(id).unwrap(); if matches!( ty, PrimT(PrimType::Nat8) @@ -78,15 +78,15 @@ fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { PrimT(PrimType::Int16) => str("Int16Array | number[]"), PrimT(PrimType::Int32) => str("Int32Array | number[]"), PrimT(PrimType::Int64) => str("BigInt64Array | bigint[]"), - _ => str("Array").append(enclose("<", pp_ty(env, t, is_ref), ">")), + _ => str("Array").append(enclose("<", pp_ty(prog, t, is_ref), ">")), } } RecordT(ref fs) => { if is_tuple(ty) { - let tuple = concat(fs.iter().map(|f| pp_ty(env, &f.typ, is_ref)), ","); + let tuple = concat(fs.iter().map(|f| pp_ty(prog, &f.typ, is_ref)), ","); enclose("[", tuple, "]") } else { - let fields = concat(fs.iter().map(|f| pp_field(env, f, is_ref)), ","); + let fields = concat(fs.iter().map(|f| pp_field(prog, f, is_ref)), ","); enclose_space("{", fields, "}") } } @@ -96,7 +96,7 @@ fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { } else { strict_concat( fs.iter() - .map(|f| enclose_space("{", pp_field(env, f, is_ref), "}")), + .map(|f| enclose_space("{", pp_field(prog, f, is_ref), "}")), " |", ) .nest(INDENT_SPACE) @@ -104,7 +104,7 @@ fn pp_ty<'a>(env: &'a IDLEnv, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a> { } FuncT(_) => str("[Principal, string]"), ServT(_) => str("Principal"), - ClassT(_, _) | KnotT(_) => unreachable!(), + ClassT(_, _) => unreachable!(), } } @@ -118,21 +118,21 @@ fn pp_label(id: &Label) -> RcDoc { } } -fn pp_field<'a>(env: &'a IDLEnv, field: &'a TypeField, is_ref: bool) -> RcDoc<'a> { +fn pp_field<'a>(prog: &'a IDLMergedProg, field: &'a TypeField, is_ref: bool) -> RcDoc<'a> { pp_label(&field.label) .append(kwd(":")) - .append(pp_ty(env, &field.typ, is_ref)) + .append(pp_ty(prog, &field.typ, is_ref)) } -fn pp_function<'a>(env: &'a IDLEnv, func: &'a FuncType) -> RcDoc<'a> { - let args = func.args.iter().map(|arg| pp_ty(env, &arg.typ, true)); +fn pp_function<'a>(prog: &'a IDLMergedProg, func: &'a FuncType) -> RcDoc<'a> { + let args = func.args.iter().map(|arg| pp_ty(prog, &arg.typ, true)); let args = enclose("[", concat(args, ","), "]"); let rets = match func.rets.len() { 0 => str("undefined"), - 1 => pp_ty(env, &func.rets[0], true), + 1 => pp_ty(prog, &func.rets[0], true), _ => enclose( "[", - concat(func.rets.iter().map(|ty| pp_ty(env, ty, true)), ","), + concat(func.rets.iter().map(|ty| pp_ty(prog, ty, true)), ","), "]", ), }; @@ -143,12 +143,12 @@ fn pp_function<'a>(env: &'a IDLEnv, func: &'a FuncType) -> RcDoc<'a> { ) } -fn pp_service<'a>(env: &'a IDLEnv, serv: &'a [Binding]) -> RcDoc<'a> { +fn pp_service<'a>(prog: &'a IDLMergedProg, serv: &'a [Binding]) -> RcDoc<'a> { let doc = concat( serv.iter().map(|Binding { id, typ }| { let func = match typ { - IDLType::FuncT(ref func) => pp_function(env, func), - _ => pp_ty(env, typ, false), + IDLType::FuncT(ref func) => pp_function(prog, func), + _ => pp_ty(prog, typ, false), }; quote_ident(id).append(kwd(":")).append(func) }), @@ -157,54 +157,54 @@ fn pp_service<'a>(env: &'a IDLEnv, serv: &'a [Binding]) -> RcDoc<'a> { enclose_space("{", doc, "}") } -fn pp_defs<'a>(env: &'a IDLEnv, def_list: &'a [&'a str]) -> RcDoc<'a> { +fn pp_defs<'a>(prog: &'a IDLMergedProg, def_list: &'a [&'a str]) -> RcDoc<'a> { lines(def_list.iter().map(|id| { - let ty = env.find_type(id).unwrap(); + let ty = prog.find_type(id).unwrap(); let export = match ty { IDLType::RecordT(_) if !ty.is_tuple() => kwd("export interface") .append(ident(id)) .append(" ") - .append(pp_ty(env, ty, false)), + .append(pp_ty(prog, ty, false)), IDLType::ServT(ref serv) => kwd("export interface") .append(ident(id)) .append(" ") - .append(pp_service(env, serv)), + .append(pp_service(prog, serv)), IDLType::FuncT(ref func) => kwd("export type") .append(ident(id)) .append(" = ") - .append(pp_function(env, func)) + .append(pp_function(prog, func)) .append(";"), _ => kwd("export type") .append(ident(id)) .append(" = ") - .append(pp_ty(env, ty, false)) + .append(pp_ty(prog, ty, false)) .append(";"), }; export })) } -fn pp_actor<'a>(env: &'a IDLEnv, ty: &'a IDLType) -> RcDoc<'a> { +fn pp_actor<'a>(prog: &'a IDLMergedProg, ty: &'a IDLType) -> RcDoc<'a> { match ty { - IDLType::ServT(ref serv) => kwd("export interface _SERVICE").append(pp_service(env, serv)), + IDLType::ServT(ref serv) => kwd("export interface _SERVICE").append(pp_service(prog, serv)), IDLType::VarT(id) => kwd("export interface _SERVICE extends") .append(str(id)) .append(str(" {}")), - IDLType::ClassT(_, t) => pp_actor(env, t), + IDLType::ClassT(_, t) => pp_actor(prog, t), _ => unreachable!(), } } -pub fn compile(env: &IDLEnv) -> String { +pub fn compile(prog: &IDLMergedProg) -> String { let header = r#"import type { Principal } from '@dfinity/principal'; import type { ActorMethod } from '@dfinity/agent'; import type { IDL } from '@dfinity/candid'; "#; - let def_list = env.types_ids(); - let defs = pp_defs(env, &def_list); - let actor = match &env.actor { + let def_list = prog.types_ids(); + let defs = pp_defs(prog, &def_list); + let actor = match &prog.actor { None => RcDoc::nil(), - Some(actor) => pp_actor(env, actor) + Some(actor) => pp_actor(prog, actor) .append(RcDoc::line()) .append("export declare const idlFactory: IDL.InterfaceFactory;") .append(RcDoc::line()) diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 6a063258a..0378c2ea2 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use candid::types::syntax::{IDLEnv, IDLType, PrimType}; +use candid::types::syntax::{IDLMergedProg, IDLType, PrimType}; use serde::de::DeserializeOwned; use std::collections::{BTreeMap, BTreeSet}; use toml::{Table, Value}; @@ -12,7 +12,7 @@ pub struct State<'a, T: ConfigState> { pub stats: BTreeMap, u32>, pub config: T, pub config_source: BTreeMap>, - pub env: &'a IDLEnv, + pub prog: &'a IDLMergedProg, } pub struct ConfigBackup { config: T, @@ -36,7 +36,7 @@ pub enum ScopePos { } impl<'a, T: ConfigState> State<'a, T> { - pub fn new(tree: &'a ConfigTree, env: &'a IDLEnv) -> Self { + pub fn new(tree: &'a ConfigTree, prog: &'a IDLMergedProg) -> Self { let mut config = T::default(); let mut config_source = BTreeMap::new(); if let Some(state) = &tree.state { @@ -53,7 +53,7 @@ impl<'a, T: ConfigState> State<'a, T> { stats: BTreeMap::new(), config, config_source, - env, + prog, } } /// Match paths in the scope first. If `scope` is None, clear the scope. @@ -408,7 +408,6 @@ fn path_name(t: &IDLType) -> String { IDLType::FuncT(_) => "func", IDLType::ServT(_) => "service", IDLType::ClassT(..) => "func:init", - IDLType::KnotT(id) => id.name, } .to_string() } @@ -523,8 +522,8 @@ Vec = { width = 2, size = 10 } t.clone(), ); assert_eq!(tree.max_depth, 4); - let env = IDLEnv::new(); - let mut state = State::new(&tree, &env); + let prog = IDLMergedProg::new(); + let mut state = State::new(&tree, &prog); state.with_scope( &Some(Scope { method: "f", diff --git a/rust/candid_parser/src/random.rs b/rust/candid_parser/src/random.rs index dce64952b..1c3f9e517 100644 --- a/rust/candid_parser/src/random.rs +++ b/rust/candid_parser/src/random.rs @@ -1,7 +1,7 @@ use super::configs::{ConfigState, Configs, Context, Scope, State, StateElem}; use crate::{Error, Result}; use arbitrary::{unstructured::Int, Arbitrary, Unstructured}; -use candid::types::syntax::{IDLEnv, IDLType, PrimType, TypeField}; +use candid::types::syntax::{IDLMergedProg, IDLType, PrimType, TypeField}; use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::{Deserialize, TypeEnv}; use std::collections::HashSet; @@ -126,7 +126,7 @@ impl RandState<'_> { } let res = Ok(match ty { IDLType::VarT(id) => { - let ty = self.0.env.rec_find_type(id).map_err(Error::msg)?; + let ty = self.0.prog.rec_find_type(id).map_err(Error::msg)?; self.any(u, ty)? } IDLType::PrimT(PrimType::Null) => IDLValue::Null, @@ -191,7 +191,7 @@ impl RandState<'_> { { [1, 0] } else { - [1, size(self.0.env, t).unwrap_or(MAX_DEPTH)] + [1, size(self.0.prog, t).unwrap_or(MAX_DEPTH)] }; let idx = arbitrary_variant(u, &depths)?; if idx == 0 { @@ -203,7 +203,7 @@ impl RandState<'_> { IDLType::VecT(t) => { self.0.update_stats("width"); let width = self.0.config.width.or_else(|| { - let elem_size = size(self.0.env, t).unwrap_or(MAX_DEPTH); + let elem_size = size(self.0.prog, t).unwrap_or(MAX_DEPTH); self.0.update_stats("size"); Some(std::cmp::max(0, self.0.config.size.unwrap_or(0)) as usize / elem_size) }); @@ -233,7 +233,7 @@ impl RandState<'_> { IDLType::VariantT(fs) => { let choices = fs .iter() - .map(|TypeField { typ, .. }| size(self.0.env, typ).unwrap_or(MAX_DEPTH)); + .map(|TypeField { typ, .. }| size(self.0.prog, typ).unwrap_or(MAX_DEPTH)); self.0.update_stats("size"); self.0.update_stats("depth"); let sizes: Vec<_> = if self.0.config.depth.is_some_and(|d| d <= 0) @@ -277,7 +277,7 @@ impl RandState<'_> { pub fn any( seed: &[u8], configs: Configs, - env: &IDLEnv, + prog: &IDLMergedProg, types: &[IDLType], scope: &Option, ) -> Result { @@ -285,7 +285,7 @@ pub fn any( let tree = super::configs::ConfigTree::from_configs("random", configs)?; let mut args = Vec::new(); for (i, t) in types.iter().enumerate() { - let mut state = State::new(&tree, env); + let mut state = State::new(&tree, prog); state.with_scope(scope, i); let mut state = RandState(state); state.0.push_state(&StateElem::Label(&i.to_string())); @@ -295,13 +295,13 @@ pub fn any( Ok(IDLArgs { args }) } -fn size_helper(env: &IDLEnv, seen: &mut HashSet, t: &IDLType) -> Option { +fn size_helper(prog: &IDLMergedProg, seen: &mut HashSet, t: &IDLType) -> Option { use IDLType::*; Some(match t { VarT(id) => { if seen.insert(id.to_string()) { - let ty = env.rec_find_type(id).unwrap(); - let res = size_helper(env, seen, ty)?; + let ty = prog.rec_find_type(id).unwrap(); + let res = size_helper(prog, seen, ty)?; seen.remove(id); res } else { @@ -309,19 +309,19 @@ fn size_helper(env: &IDLEnv, seen: &mut HashSet, t: &IDLType) -> Option< } } PrimT(PrimType::Empty) => 0, - OptT(t) => 1 + size_helper(env, seen, t)?, - VecT(t) => 1 + size_helper(env, seen, t)? * 2, + OptT(t) => 1 + size_helper(prog, seen, t)?, + VecT(t) => 1 + size_helper(prog, seen, t)? * 2, RecordT(fs) => { let mut sum = 0; for TypeField { typ, .. } in fs.iter() { - sum += size_helper(env, seen, typ)?; + sum += size_helper(prog, seen, typ)?; } 1 + sum } VariantT(fs) => { let mut max = 0; for TypeField { typ, .. } in fs.iter() { - let s = size_helper(env, seen, typ)?; + let s = size_helper(prog, seen, typ)?; if s > max { max = s; }; @@ -332,9 +332,9 @@ fn size_helper(env: &IDLEnv, seen: &mut HashSet, t: &IDLType) -> Option< }) } -fn size(env: &IDLEnv, t: &IDLType) -> Option { +fn size(prog: &IDLMergedProg, t: &IDLType) -> Option { let mut seen = HashSet::new(); - size_helper(env, &mut seen, t) + size_helper(prog, &mut seen, t) } fn choose_range(u: &mut Unstructured, ranges: &[std::ops::RangeInclusive]) -> Result { diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 418e11f3c..9f11f7089 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -1,48 +1,29 @@ use crate::{parse_idl_prog, pretty_parse_idl_prog, Error, Result}; use candid::types::{ syntax::{ - Binding, Dec, FuncType, IDLArgType, IDLEnv, IDLInitArgs, IDLProg, IDLType, PrimType, - TypeField, + Binding, Dec, IDLArgType, IDLInitArgs, IDLMergedProg, IDLProg, IDLType, PrimType, TypeField, }, ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; pub struct Env<'a> { - te: &'a mut TypeEnv, - idl_env: &'a mut IDLEnv, - pre: bool, -} - -impl Env<'_> { - fn insert_type(&mut self, id: String, t: Type, idl_type: IDLType) { - self.te.0.insert(id.clone(), t); - self.idl_env.insert_binding(Binding { id, typ: idl_type }); - } - - fn insert_id(&mut self, id: String) -> Result<()> { - let duplicate = self.te.0.insert(id.clone(), TypeInner::Unknown.into()); - if duplicate.is_some() { - return Err(Error::msg(format!("duplicate binding for {id}"))); - } - self.idl_env.insert_unknown(id); - Ok(()) - } + pub te: &'a mut TypeEnv, + pub pre: bool, } /// Convert candid AST to internal Type pub fn ast_to_type(env: &TypeEnv, ast: &IDLType) -> Result { let env = Env { te: &mut env.clone(), - idl_env: &mut IDLEnv::new(), pre: false, }; - map_type(&env, ast) + check_type(&env, ast) } -fn map_prim(prim: &PrimType) -> Type { +fn check_prim(prim: &PrimType) -> Type { match prim { PrimType::Nat => TypeInner::Nat, PrimType::Nat8 => TypeInner::Nat8, @@ -65,38 +46,38 @@ fn map_prim(prim: &PrimType) -> Type { .into() } -pub fn map_type(env: &Env, t: &IDLType) -> Result { +pub fn check_type(env: &Env, t: &IDLType) -> Result { match t { - IDLType::PrimT(prim) => Ok(map_prim(prim)), + IDLType::PrimT(prim) => Ok(check_prim(prim)), IDLType::VarT(id) => { env.te.find_type(id)?; Ok(TypeInner::Var(id.to_string()).into()) } IDLType::OptT(t) => { - let t = map_type(env, t)?; + let t = check_type(env, t)?; Ok(TypeInner::Opt(t).into()) } IDLType::VecT(t) => { - let t = map_type(env, t)?; + let t = check_type(env, t)?; Ok(TypeInner::Vec(t).into()) } IDLType::RecordT(fs) => { - let fs = map_fields(env, fs)?; + let fs = check_fields(env, fs)?; Ok(TypeInner::Record(fs).into()) } IDLType::VariantT(fs) => { - let fs = map_fields(env, fs)?; + let fs = check_fields(env, fs)?; Ok(TypeInner::Variant(fs).into()) } IDLType::PrincipalT => Ok(TypeInner::Principal.into()), IDLType::FuncT(func) => { let mut t1 = Vec::new(); for arg in func.args.iter() { - t1.push(map_arg(env, arg)?); + t1.push(check_arg(env, arg)?); } let mut t2 = Vec::new(); for t in func.rets.iter() { - t2.push(map_type(env, t)?); + t2.push(check_type(env, t)?); } if func.modes.len() > 1 { return Err(Error::msg("cannot have more than one mode")); @@ -115,33 +96,25 @@ pub fn map_type(env: &Env, t: &IDLType) -> Result { Ok(TypeInner::Func(f).into()) } IDLType::ServT(ms) => { - let ms = map_meths(env, ms)?; + let ms = check_meths(env, ms)?; Ok(TypeInner::Service(ms).into()) } - IDLType::KnotT(id) => Ok(TypeInner::Knot(id.clone()).into()), - IDLType::ClassT(args, serv) => { - let serv = map_type(env, serv)?; - let args = args - .iter() - .map(|arg| map_arg(env, arg)) - .collect::>>()?; - Ok(TypeInner::Class(args, serv).into()) - } + IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), } } -fn map_arg(env: &Env, arg: &IDLArgType) -> Result { +fn check_arg(env: &Env, arg: &IDLArgType) -> Result { Ok(ArgType { name: arg.name.clone(), - typ: map_type(env, &arg.typ)?, + typ: check_type(env, &arg.typ)?, }) } -fn map_fields(env: &Env, fs: &[TypeField]) -> Result> { +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 = map_type(env, &f.typ)?; + let ty = check_type(env, &f.typ)?; let field = Field { id: f.label.clone().into(), ty, @@ -151,11 +124,11 @@ fn map_fields(env: &Env, fs: &[TypeField]) -> Result> { Ok(res) } -fn map_meths(env: &Env, ms: &[Binding]) -> Result> { +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 = map_type(env, &meth.typ)?; + let t = check_type(env, &meth.typ)?; if !env.pre && env.te.as_func(&t).is_err() { return Err(Error::msg(format!( "method {} is a non-function type", @@ -167,113 +140,37 @@ fn map_meths(env: &Env, ms: &[Binding]) -> Result> { Ok(res) } -pub fn check_type(env: &Env, t: &IDLType) -> Result { - match t { - IDLType::PrimT(prim) => Ok(IDLType::PrimT(prim.clone())), - IDLType::VarT(id) => { - env.idl_env.assert_has_type(id).map_err(Error::msg)?; - Ok(IDLType::VarT(id.to_string())) - } - IDLType::OptT(t) => { - let t = check_type(env, t)?; - Ok(IDLType::OptT(Box::new(t))) - } - IDLType::VecT(t) => { - let t = check_type(env, t)?; - Ok(IDLType::VecT(Box::new(t))) - } - IDLType::RecordT(fs) => { - let fs = check_fields(env, fs)?; - Ok(IDLType::RecordT(fs)) - } - IDLType::VariantT(fs) => { - let fs = check_fields(env, fs)?; - Ok(IDLType::VariantT(fs)) - } - IDLType::PrincipalT => Ok(IDLType::PrincipalT), - IDLType::FuncT(func) => { - let mut t1 = Vec::new(); - for arg in func.args.iter() { - t1.push(check_arg(env, arg)?); - } - let mut t2 = Vec::new(); - for t in func.rets.iter() { - t2.push(check_type(env, t)?); - } - if func.modes.len() > 1 { - return Err(Error::msg("cannot have more than one mode")); - } - if func.modes.len() == 1 - && func.modes[0] == candid::types::FuncMode::Oneway - && !t2.is_empty() - { - return Err(Error::msg("oneway function has non-unit return type")); +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)?; + env.te.0.insert(id.to_string(), t); } - let f = FuncType { - modes: func.modes.clone(), - args: t1, - rets: t2, - }; - Ok(IDLType::FuncT(f)) - } - IDLType::ServT(ms) => { - let ms = check_meths(env, ms)?; - Ok(IDLType::ServT(ms)) + Dec::ImportType(_) | Dec::ImportServ(_) => (), } - IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - IDLType::KnotT(_) => Err(Error::msg("knot type not supported")), } + Ok(()) } -fn check_arg(env: &Env, arg: &IDLArgType) -> Result { - Ok(IDLArgType { - name: arg.name.clone(), - typ: check_type(env, &arg.typ).map_err(Error::msg)?, - }) -} - -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 typ = check_type(env, &f.typ).map_err(Error::msg)?; - let field = TypeField { - label: f.label.clone(), - typ, - }; - res.push(field); - } - Ok(res) -} - -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)?; - if !env.pre && env.idl_env.as_func(&t).is_err() { - return Err(Error::msg(format!( - "method {} is a non-function type", - meth.id - ))); +fn check_cycle(env: &TypeEnv) -> Result<()> { + fn has_cycle<'a>(seen: &mut BTreeSet<&'a str>, env: &'a TypeEnv, t: &'a Type) -> Result { + match t.as_ref() { + TypeInner::Var(id) => { + if seen.insert(id) { + let ty = env.find_type(id)?; + has_cycle(seen, env, ty) + } else { + Ok(true) + } + } + _ => Ok(false), } - res.push(Binding { - id: meth.id.to_owned(), - typ: t, - }); } - Ok(res) -} - -fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { - for dec in decs.iter() { - match dec { - Dec::TypD(Binding { id, typ }) => { - let idl_type = check_type(env, typ)?; - let t = map_type(env, &idl_type)?; - env.insert_type(id.to_string(), t, idl_type); - } - Dec::ImportType(_) | Dec::ImportServ(_) => (), + for (id, ty) in env.0.iter() { + let mut seen = BTreeSet::new(); + if has_cycle(&mut seen, env, ty)? { + return Err(Error::msg(format!("{id} has cyclic type definition"))); } } Ok(()) @@ -282,35 +179,36 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, typ: _ }) = dec { - env.insert_id(id.to_string())?; + 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}"))); + } } } env.pre = true; check_defs(env, decs)?; - env.te.check_cycle()?; + check_cycle(env.te)?; env.pre = false; check_defs(env, decs)?; Ok(()) } -fn check_actor(env: &Env, actor: &Option) -> Result> { +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)?); + } + let serv = check_type(env, t)?; + env.te.as_service(&serv)?; + Ok(Some(TypeInner::Class(args, serv).into())) + } Some(typ) => { - let serv = if let IDLType::ClassT(ts, t) = typ { - let mut args = Vec::new(); - for arg in ts.iter() { - args.push(check_arg(env, arg)?); - } - let t = check_type(env, t)?; - env.idl_env.as_service(&t).map_err(Error::msg)?; - IDLType::ClassT(args, Box::new(t)) - } else { - let serv = check_type(env, typ)?; - env.idl_env.as_service(&serv).map_err(Error::msg)?; - serv - }; - Ok(Some(serv)) + let t = check_type(env, typ)?; + env.te.as_service(&t)?; + Ok(Some(t)) } } } @@ -359,12 +257,8 @@ fn load_imports( /// Type check IDLProg and adds bindings to type environment. Returns /// the main actor if present. This function ignores the imports. -pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result> { - let mut env = Env { - te, - idl_env: &mut IDLEnv::new(), - pre: false, - }; +pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result> { + let mut env = Env { te, pre: false }; check_decs(&mut env, &prog.decs)?; check_actor(&env, &prog.actor) } @@ -373,55 +267,45 @@ pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result> { pub fn check_init_args( te: &mut TypeEnv, main_env: &TypeEnv, - idl_env: &mut IDLEnv, prog: &IDLInitArgs, ) -> Result> { - let mut env = Env { - te, - idl_env, - pre: false, - }; + let mut env = Env { te, pre: false }; check_decs(&mut env, &prog.decs)?; env.te.merge(main_env)?; let mut args = Vec::new(); for arg in prog.args.iter() { - args.push(check_arg(&env, arg).and_then(|t| { - Ok(ArgType { - name: t.name, - typ: ast_to_type(env.te, &t.typ)?, - }) - })?); + args.push(check_arg(&env, arg)?); } Ok(args) } fn merge_actor( env: &Env, - actor: &Option, - imported: &Option, + actor: &Option, + imported: &Option, file: &str, -) -> Result> { +) -> Result> { match imported { None => Err(Error::msg(format!( "Imported service file {file:?} has no main service" ))), - Some(idl_type) => { - let t = env.idl_env.trace_type(idl_type).map_err(Error::msg)?; - match &t { - IDLType::ClassT(_, _) => Err(Error::msg(format!( + Some(t) => { + let t = env.te.trace_type(t)?; + match t.as_ref() { + TypeInner::Class(_, _) => Err(Error::msg(format!( "Imported service file {file:?} has a service constructor" ))), - IDLType::ServT(meths) => match &actor { + TypeInner::Service(meths) => match actor { None => Ok(Some(t)), - Some(idl_type) => { - let t = env.idl_env.trace_type(idl_type).map_err(Error::msg)?; - let serv = env.idl_env.as_service(&t).map_err(Error::msg)?; + Some(t) => { + let t = env.te.trace_type(t)?; + let serv = env.te.as_service(&t)?; let mut ms: Vec<_> = serv.iter().chain(meths.iter()).cloned().collect(); - ms.sort_unstable_by(|a, b| a.id.partial_cmp(&b.id).unwrap()); - check_unique(ms.iter().map(|m| &m.id)).map_err(|e| { + ms.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + check_unique(ms.iter().map(|m| &m.0)).map_err(|e| { Error::msg(format!("Duplicate imported method name: {e}")) })?; - let res = IDLType::ServT(ms); + let res: Type = TypeInner::Service(ms).into(); Ok(Some(res)) } }, @@ -431,7 +315,7 @@ fn merge_actor( } } -fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option)> { +fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option, IDLMergedProg)> { let base = if file.is_absolute() { file.parent().unwrap().to_path_buf() } else { @@ -464,18 +348,18 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option< .collect(); let mut te = TypeEnv::new(); - let mut idl_env = IDLEnv::new(); let mut env = Env { te: &mut te, - idl_env: &mut idl_env, pre: false, }; + let mut idl_merged_prog = IDLMergedProg::new(); - let mut actor: Option = None; + let mut actor: Option = None; for (include_serv, path, name) in imports.iter() { let code = std::fs::read_to_string(path)?; let code = parse_idl_prog(&code)?; check_decs(&mut env, &code.decs)?; + idl_merged_prog.add_decs(&code.decs); if *include_serv { let t = check_actor(&env, &code.actor)?; actor = merge_actor(&env, &actor, &t, name)?; @@ -483,20 +367,22 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, IDLEnv, Option< } check_decs(&mut env, &prog.decs)?; + idl_merged_prog.add_decs(&prog.decs); let mut res = check_actor(&env, &prog.actor)?; if actor.is_some() { res = merge_actor(&env, &res, &actor, "")?; } - idl_env.set_actor(res.clone()); - Ok((te, idl_env, res)) + idl_merged_prog.set_actor(res.clone().map(Into::into)); + + Ok((te, res, idl_merged_prog)) } /// Type check did file including the imports. -pub fn check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { +pub fn check_file(file: &Path) -> Result<(TypeEnv, Option, IDLMergedProg)> { check_file_(file, false) } -pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, IDLEnv, Option)> { +pub fn pretty_check_file(file: &Path) -> Result<(TypeEnv, Option, IDLMergedProg)> { check_file_(file, true) } diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 0e2e116c0..8de290696 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -1,9 +1,7 @@ -use crate::{ - check_prog, pretty_check_file, pretty_parse_idl_prog, typing::ast_to_type, Error, Result, -}; +use crate::{check_prog, pretty_check_file, pretty_parse_idl_prog, Error, Result}; use candid::{ types::{ - syntax::{Binding, IDLEnv, IDLType}, + syntax::{Binding, IDLMergedProg, IDLType}, Type, TypeInner, }, TypeEnv, @@ -16,15 +14,15 @@ pub enum CandidSource<'a> { } impl CandidSource<'_> { - pub fn load(&self) -> Result<(TypeEnv, IDLEnv, Option)> { + pub fn load(&self) -> Result<(TypeEnv, Option, IDLMergedProg)> { Ok(match self { CandidSource::File(path) => pretty_check_file(path)?, CandidSource::Text(str) => { let ast = pretty_parse_idl_prog("", str)?; let mut env = TypeEnv::new(); - let idl_env = IDLEnv::from(&ast); let actor = check_prog(&mut env, &ast)?; - (env, idl_env, actor) + let idl_merged_prog = IDLMergedProg::from(ast); + (env, actor, idl_merged_prog) } }) } @@ -32,14 +30,10 @@ impl CandidSource<'_> { /// Check compatibility of two service types pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { - let (mut env, _, t1) = new.load()?; - let t1 = t1 - .ok_or_else(|| Error::msg("new interface has no main service type")) - .and_then(|t| ast_to_type(&env, &t))?; - let (env2, _, t2) = old.load()?; - let t2 = t2 - .ok_or_else(|| Error::msg("old interface has no main service type")) - .and_then(|t| ast_to_type(&env, &t))?; + let (mut env, t1, _) = new.load()?; + let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; + let (env2, t2, _) = old.load()?; + let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); candid::types::subtype::subtype(&mut gamma, &env, &t1, &t2)?; @@ -48,14 +42,10 @@ pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { /// Check structural equality of two service types pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { - let (mut env, _, t1) = left.load()?; - let t1 = t1 - .ok_or_else(|| Error::msg("left interface has no main service type")) - .and_then(|t| ast_to_type(&env, &t))?; - let (env2, _, t2) = right.load()?; - let t2 = t2 - .ok_or_else(|| Error::msg("right interface has no main service type")) - .and_then(|t| ast_to_type(&env, &t))?; + let (mut env, t1, _) = left.load()?; + let t1 = t1.ok_or_else(|| Error::msg("left interface has no main service type"))?; + let (env2, t2, _) = right.load()?; + let t2 = t2.ok_or_else(|| Error::msg("right interface has no main service type"))?; let mut gamma = std::collections::HashSet::new(); let t2 = env.merge_type(env2, t2); candid::types::subtype::equal(&mut gamma, &env, &t1, &t2)?; @@ -66,10 +56,8 @@ pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { /// If the original did file contains imports, the output flattens the type definitions. /// For now, the comments from the original did file is omitted. pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, Type))> { - let (env, _, serv) = candid.load()?; - let serv = serv - .ok_or_else(|| Error::msg("the Candid interface has no main service type")) - .and_then(|t| ast_to_type(&env, &t))?; + let (env, serv, _) = candid.load()?; + let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; let serv = env.trace_type(&serv)?; Ok(match serv.as_ref() { TypeInner::Class(args, ty) => ( @@ -80,8 +68,8 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, _ => unreachable!(), }) } -pub fn get_metadata(env: &IDLEnv) -> Option { - let serv = env.actor.clone()?; +pub fn get_metadata(env: &IDLMergedProg) -> Option { + let serv = env.actor.as_ref()?; let serv = env.trace_type(&serv).ok()?; let serv = match &serv { IDLType::ClassT(_, ty) => ty.as_ref(), @@ -89,11 +77,11 @@ pub fn get_metadata(env: &IDLEnv) -> Option { _ => unreachable!(), }; let def_list = crate::bindings::analysis::chase_actor(env, serv).ok()?; - let mut filtered = IDLEnv::new(); + let mut filtered = IDLMergedProg::new(); for d in def_list { - if let Ok((id, typ)) = env.find_binding(d) { + if let Ok(typ) = env.find_type(d) { filtered.insert_binding(Binding { - id: id.to_string(), + id: d.to_string(), typ: typ.clone(), }); } @@ -108,17 +96,15 @@ pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> { use crate::{parse_idl_init_args, typing::check_init_args}; use candid::types::TypeInner; let candid = CandidSource::Text(candid); - let (env, mut idl_env, serv) = candid.load()?; - let serv = serv - .ok_or_else(|| Error::msg("the Candid interface has no main service type")) - .and_then(|t| ast_to_type(&env, &t))?; + let (env, serv, _) = candid.load()?; + let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?; let serv = env.trace_type(&serv)?; match serv.as_ref() { TypeInner::Class(_, _) => Ok((env, serv)), TypeInner::Service(_) => { let prog = parse_idl_init_args(init)?; let mut env2 = TypeEnv::new(); - let args = check_init_args(&mut env2, &env, &mut idl_env, &prog)?; + let args = check_init_args(&mut env2, &env, &prog)?; Ok((env2, TypeInner::Class(args, serv).into())) } _ => unreachable!(), @@ -132,11 +118,10 @@ pub fn check_rust_type(candid_args: &str) -> Result<()> { use candid::types::{internal::TypeContainer, subtype::equal, TypeEnv}; let parsed = parse_idl_init_args(candid_args)?; let mut env = TypeEnv::new(); - let mut idl_env = IDLEnv::new(); - let args = check_init_args(&mut env, &TypeEnv::new(), &mut idl_env, &parsed)?; + let args = check_init_args(&mut env, &TypeEnv::new(), &parsed)?; let mut rust_env = TypeContainer::new(); let ty = rust_env.add::(); - let ty = env.merge_type(rust_env.idl_env.into(), ty.into()); + let ty = env.merge_type(rust_env.env, ty); let mut gamma = std::collections::HashSet::new(); equal(&mut gamma, &env, &args[0].typ, &ty)?; Ok(()) diff --git a/rust/candid_parser/tests/assets/ok/undefine.fail b/rust/candid_parser/tests/assets/ok/undefine.fail index b59b1f2be..5fc7eee7e 100644 --- a/rust/candid_parser/tests/assets/ok/undefine.fail +++ b/rust/candid_parser/tests/assets/ok/undefine.fail @@ -1 +1 @@ -Type identifier not found: a +Unbound type identifier a diff --git a/rust/candid_parser/tests/parse_type.rs b/rust/candid_parser/tests/parse_type.rs index 0cd3ce956..d78c8f96b 100644 --- a/rust/candid_parser/tests/parse_type.rs +++ b/rust/candid_parser/tests/parse_type.rs @@ -40,10 +40,10 @@ fn compiler_test(resource: &str) { let candid_path = base_path.join(filename); match check_file(&candid_path) { - Ok((_, idl_env, _)) => { + Ok((_, _, idl_merged_prog)) => { { let mut output = mint.new_goldenfile(filename.with_extension("did")).unwrap(); - let content = compile(&idl_env); + let content = compile(&idl_merged_prog); // Type check output let ast = parse_idl_prog(&content).unwrap(); check_prog(&mut TypeEnv::new(), &ast).unwrap(); @@ -51,13 +51,14 @@ fn compiler_test(resource: &str) { } { match filename.file_name().unwrap().to_str().unwrap() { - "unicode.did" | "escape.did" => { - check_error(|| motoko::compile(&idl_env), "not a valid Motoko id") - } + "unicode.did" | "escape.did" => check_error( + || motoko::compile(&idl_merged_prog), + "not a valid Motoko id", + ), _ => { let mut output = mint.new_goldenfile(filename.with_extension("mo")).unwrap(); - let content = motoko::compile(&idl_env); + let content = motoko::compile(&idl_merged_prog); writeln!(output, "{content}").unwrap(); } } @@ -87,20 +88,20 @@ fn compiler_test(resource: &str) { _ => (), } let mut output = mint.new_goldenfile(filename.with_extension("rs")).unwrap(); - let (content, unused) = rust::compile(&config, &idl_env, external); + let (content, unused) = rust::compile(&config, &idl_merged_prog, external); assert!(unused.is_empty()); writeln!(output, "{content}").unwrap(); } { let mut output = mint.new_goldenfile(filename.with_extension("js")).unwrap(); - let content = javascript::compile(&idl_env); + let content = javascript::compile(&idl_merged_prog); writeln!(output, "{content}").unwrap(); } { let mut output = mint .new_goldenfile(filename.with_extension("d.ts")) .unwrap(); - let content = typescript::compile(&idl_env); + let content = typescript::compile(&idl_merged_prog); writeln!(output, "{content}").unwrap(); } } diff --git a/rust/candid_parser/tests/value.rs b/rust/candid_parser/tests/value.rs index 6a70c8b46..aa6e0a281 100644 --- a/rust/candid_parser/tests/value.rs +++ b/rust/candid_parser/tests/value.rs @@ -1,7 +1,6 @@ use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue}; use candid::types::{Label, TypeEnv}; use candid::{decode_args, decode_one, Decode}; -use candid_parser::typing::ast_to_type; use candid_parser::{parse_idl_args, parse_idl_prog, typing::check_prog}; #[test] @@ -35,7 +34,6 @@ service : { let ast = parse_idl_prog(candid).unwrap(); let mut env = TypeEnv::new(); let actor = check_prog(&mut env, &ast).unwrap().unwrap(); - let actor = ast_to_type(&env, &actor).unwrap(); let method = env.get_method(&actor, "f").unwrap(); { let args = parse_idl_args("(42,42,42,42)").unwrap(); From e41b78bae14914eae6fc42f88df1c1b455fa196e Mon Sep 17 00:00:00 2001 From: ilbertt Date: Mon, 23 Jun 2025 15:50:52 +0200 Subject: [PATCH 25/34] style: clippy --- rust/candid_parser/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 8de290696..89c870437 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -70,7 +70,7 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec, (TypeEnv, } pub fn get_metadata(env: &IDLMergedProg) -> Option { let serv = env.actor.as_ref()?; - let serv = env.trace_type(&serv).ok()?; + let serv = env.trace_type(serv).ok()?; let serv = match &serv { IDLType::ClassT(_, ty) => ty.as_ref(), IDLType::ServT(_) => &serv, From 9a1291adfd36d6c0cea8e529827e0e629230ab38 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 24 Jun 2025 13:55:46 +0200 Subject: [PATCH 26/34] fix: use idl merged prog in candid_derive --- rust/candid/src/types/internal.rs | 23 +++++-- rust/candid/src/types/subtype.rs | 17 ++--- rust/candid/src/types/syntax.rs | 103 +++++++----------------------- rust/candid/src/types/type_env.rs | 102 ++++++++++++++++++++++++++++- rust/candid/tests/types.rs | 75 ++++++++++++---------- rust/candid_derive/src/func.rs | 15 ++++- rust/candid_parser/src/typing.rs | 2 +- tools/didc/src/main.rs | 38 +++++------ 8 files changed, 225 insertions(+), 150 deletions(-) diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index dcb5531d7..931da5cec 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -1,5 +1,6 @@ use super::CandidType; use crate::idl_hash; +use crate::types::syntax::IDLType; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BTreeMap; @@ -177,6 +178,12 @@ impl TypeContainer { } .into() } + + pub fn as_idl_type(&self, ty: &Type) -> IDLType { + // The knot type has already been converted to a var type when adding the type to the env, + // see `self.add` and `self.go` + self.env.as_idl_type(ty) + } } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -325,20 +332,22 @@ impl Type { #[cfg(feature = "printer")] impl fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let env = crate::TypeEnv::new(); write!( f, "{}", - crate::pretty::candid::pp_ty(&self.clone().into()).pretty(80), + crate::pretty::candid::pp_ty(&env.as_idl_type(self)).pretty(80), ) } } #[cfg(feature = "printer")] impl fmt::Display for TypeInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let env = crate::TypeEnv::new(); write!( f, "{}", - crate::pretty::candid::pp_ty(&Type::from(self.clone()).into()).pretty(80), + crate::pretty::candid::pp_ty(&env.inner_as_idl_type(self)).pretty(80), ) } } @@ -488,10 +497,11 @@ pub struct Field { #[cfg(feature = "printer")] impl fmt::Display for Field { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let env = crate::TypeEnv::new(); write!( f, "{}", - crate::pretty::candid::pp_field(&self.clone().into(), false).pretty(80) + crate::pretty::candid::pp_field(&env.field_to_idl_field(self), false).pretty(80) ) } } @@ -565,10 +575,11 @@ pub struct ArgType { #[cfg(feature = "printer")] impl fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let env = crate::TypeEnv::new(); write!( f, "{}", - crate::pretty::candid::pp_function(&self.clone().into()).pretty(80), + crate::pretty::candid::pp_function(&env.func_to_idl_func(self)).pretty(80), ) } } @@ -735,6 +746,10 @@ pub(crate) fn env_id(id: TypeId, t: Type) { }); } +pub fn get_id(t: &Type) -> TypeId { + ID.with(|n| n.borrow().get(t).cloned().unwrap()) +} + pub fn get_type(_v: &T) -> Type where T: CandidType, diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 7b0f1d614..930fc6e5e 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -253,8 +253,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( let init_2 = to_tuple(&init2_typ); equal(gamma, env, &init_1, &init_2).context(format!( "Mismatch in init args: {} and {}", - pp_args(init1), - pp_args(init2) + pp_args(env, init1), + pp_args(env, init2) ))?; equal(gamma, env, ty1, ty2) } @@ -316,14 +316,9 @@ fn pp_args(args: &[crate::types::ArgType]) -> String { s } #[cfg(feature = "printer")] -fn pp_args(args: &[crate::types::ArgType]) -> String { +fn pp_args(env: &TypeEnv, args: &[crate::types::ArgType]) -> String { use crate::pretty::candid::pp_args; - pp_args( - args.iter() - .map(|arg| arg.clone().into()) - .collect::>() - .as_slice(), - ) - .pretty(80) - .to_string() + pp_args(&env.arg_types_to_idl_arg_types(args)) + .pretty(80) + .to_string() } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 2484135e9..933b85524 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -84,57 +84,6 @@ impl From for Type { } } -impl From for IDLType { - fn from(t: Type) -> Self { - match t.as_ref() { - TypeInner::Null => IDLType::PrimT(PrimType::Null), - TypeInner::Bool => IDLType::PrimT(PrimType::Bool), - TypeInner::Nat => IDLType::PrimT(PrimType::Nat), - TypeInner::Int => IDLType::PrimT(PrimType::Int), - TypeInner::Nat8 => IDLType::PrimT(PrimType::Nat8), - TypeInner::Nat16 => IDLType::PrimT(PrimType::Nat16), - TypeInner::Nat32 => IDLType::PrimT(PrimType::Nat32), - TypeInner::Nat64 => IDLType::PrimT(PrimType::Nat64), - TypeInner::Int8 => IDLType::PrimT(PrimType::Int8), - TypeInner::Int16 => IDLType::PrimT(PrimType::Int16), - TypeInner::Int32 => IDLType::PrimT(PrimType::Int32), - TypeInner::Int64 => IDLType::PrimT(PrimType::Int64), - TypeInner::Float32 => IDLType::PrimT(PrimType::Float32), - TypeInner::Float64 => IDLType::PrimT(PrimType::Float64), - TypeInner::Text => IDLType::PrimT(PrimType::Text), - TypeInner::Reserved => IDLType::PrimT(PrimType::Reserved), - TypeInner::Empty => IDLType::PrimT(PrimType::Empty), - TypeInner::Var(id) => IDLType::VarT(id.to_string()), - TypeInner::Opt(t) => IDLType::OptT(Box::new(t.clone().into())), - TypeInner::Vec(t) => IDLType::VecT(Box::new(t.clone().into())), - TypeInner::Record(fields) => { - IDLType::RecordT(fields.iter().map(|t| t.clone().into()).collect()) - } - TypeInner::Variant(fields) => { - IDLType::VariantT(fields.iter().map(|t| t.clone().into()).collect()) - } - TypeInner::Func(func) => IDLType::FuncT(func.clone().into()), - TypeInner::Service(methods) => IDLType::ServT( - methods - .iter() - .map(|t| Binding { - id: t.0.clone(), - typ: t.1.clone().into(), - }) - .collect(), - ), - TypeInner::Class(args, t) => IDLType::ClassT( - args.iter().map(|t| t.clone().into()).collect(), - Box::new(t.clone().into()), - ), - TypeInner::Principal => IDLType::PrincipalT, - TypeInner::Knot(_) | TypeInner::Unknown | TypeInner::Future => { - panic!("Unknown type: {:?}", t) - } - } - } -} - #[derive(Debug, Clone)] pub struct IDLTypes { pub args: Vec, @@ -197,15 +146,6 @@ impl From for Function { } } -impl From for FuncType { - fn from(t: Function) -> Self { - FuncType { - modes: t.modes, - args: t.args.into_iter().map(|t| t.into()).collect(), - rets: t.rets.into_iter().map(|t| t.into()).collect(), - } - } -} #[derive(Debug, Clone, PartialEq, Eq)] pub struct IDLArgType { pub typ: IDLType, @@ -221,15 +161,6 @@ impl From for ArgType { } } -impl From for IDLArgType { - fn from(t: ArgType) -> Self { - IDLArgType { - typ: t.typ.into(), - name: t.name, - } - } -} - impl IDLArgType { pub fn new(typ: IDLType) -> Self { Self { typ, name: None } @@ -263,15 +194,6 @@ impl From for Field { } } -impl From for TypeField { - fn from(t: Field) -> Self { - TypeField { - label: (*t.id).clone(), - typ: t.ty.into(), - } - } -} - #[derive(Debug, Clone)] pub enum Dec { TypD(Binding), @@ -285,12 +207,26 @@ pub struct Binding { pub typ: IDLType, } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct IDLProg { pub decs: Vec, pub actor: Option, } +impl IDLProg { + pub fn new() -> Self { + Self::default() + } + + pub fn add_binding(&mut self, binding: Binding) { + self.decs.push(Dec::TypD(binding)); + } + + pub fn set_actor(&mut self, actor: Option) { + self.actor = actor; + } +} + #[derive(Debug)] pub struct IDLInitArgs { pub decs: Vec, @@ -396,4 +332,13 @@ impl IDLMergedProg { _ => Err(format!("not a function type: {:?}", t)), } } + + pub fn get_method<'a>(&'a self, t: &'a IDLType, id: &'a str) -> Result<&'a FuncType, String> { + for binding in self.service_methods(t)? { + if binding.id == id { + return self.as_func(&binding.typ); + } + } + Err(format!("cannot find method {id}")) + } } diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 964d60dc0..5dd1d58c6 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,4 +1,5 @@ -use crate::types::{Function, Type, TypeInner}; +use crate::types::syntax::{Binding, FuncType, IDLArgType, IDLType, PrimType, TypeField}; +use crate::types::{ArgType, Field, Function, Type, TypeInner}; use crate::{Error, Result}; use std::collections::{BTreeMap, BTreeSet}; @@ -9,6 +10,7 @@ impl TypeEnv { pub fn new() -> Self { TypeEnv(BTreeMap::new()) } + pub fn merge<'a>(&'a mut self, env: &TypeEnv) -> Result<&'a mut Self> { for (k, v) in &env.0 { let entry = self.0.entry(k.to_string()).or_insert_with(|| v.clone()); @@ -18,6 +20,7 @@ impl TypeEnv { } Ok(self) } + pub fn merge_type(&mut self, env: TypeEnv, ty: Type) -> Type { let tau: BTreeMap = env .0 @@ -35,12 +38,14 @@ impl TypeEnv { } ty.subst(&tau) } + pub fn find_type(&self, name: &str) -> Result<&Type> { match self.0.get(name) { None => Err(Error::msg(format!("Unbound type identifier {name}"))), Some(t) => Ok(t), } } + pub fn rec_find_type(&self, name: &str) -> Result<&Type> { let t = self.find_type(name)?; match t.as_ref() { @@ -48,6 +53,7 @@ impl TypeEnv { _ => Ok(t), } } + pub fn trace_type<'a>(&'a self, t: &'a Type) -> Result { match t.as_ref() { TypeInner::Var(id) => self.trace_type(self.find_type(id)?), @@ -57,6 +63,7 @@ impl TypeEnv { _ => Ok(t.clone()), } } + pub fn as_func<'a>(&'a self, t: &'a Type) -> Result<&'a Function> { match t.as_ref() { TypeInner::Func(f) => Ok(f), @@ -64,6 +71,7 @@ impl TypeEnv { _ => Err(Error::msg(format!("not a function type: {t}"))), } } + pub fn as_service<'a>(&'a self, t: &'a Type) -> Result<&'a [(String, Type)]> { match t.as_ref() { TypeInner::Service(s) => Ok(s), @@ -72,6 +80,7 @@ impl TypeEnv { _ => Err(Error::msg(format!("not a service type: {t}"))), } } + pub fn get_method<'a>(&'a self, t: &'a Type, id: &'a str) -> Result<&'a Function> { for (meth, ty) in self.as_service(t)? { if meth == id { @@ -80,6 +89,7 @@ impl TypeEnv { } Err(Error::msg(format!("cannot find method {id}"))) } + fn is_empty<'a>( &'a self, res: &mut BTreeMap<&'a str, Option>, @@ -115,6 +125,7 @@ impl TypeEnv { Some(Some(b)) => Ok(*b), } } + pub fn replace_empty(&mut self) -> Result<()> { let mut res = BTreeMap::new(); for name in self.0.keys() { @@ -154,6 +165,95 @@ impl TypeEnv { } Ok(()) } + + pub fn inner_as_idl_type(&self, ty: &TypeInner) -> IDLType { + match ty { + TypeInner::Null => IDLType::PrimT(PrimType::Null), + TypeInner::Bool => IDLType::PrimT(PrimType::Bool), + TypeInner::Nat => IDLType::PrimT(PrimType::Nat), + TypeInner::Int => IDLType::PrimT(PrimType::Int), + TypeInner::Nat8 => IDLType::PrimT(PrimType::Nat8), + TypeInner::Nat16 => IDLType::PrimT(PrimType::Nat16), + TypeInner::Nat32 => IDLType::PrimT(PrimType::Nat32), + TypeInner::Nat64 => IDLType::PrimT(PrimType::Nat64), + TypeInner::Int8 => IDLType::PrimT(PrimType::Int8), + TypeInner::Int16 => IDLType::PrimT(PrimType::Int16), + TypeInner::Int32 => IDLType::PrimT(PrimType::Int32), + TypeInner::Int64 => IDLType::PrimT(PrimType::Int64), + TypeInner::Float32 => IDLType::PrimT(PrimType::Float32), + TypeInner::Float64 => IDLType::PrimT(PrimType::Float64), + TypeInner::Text => IDLType::PrimT(PrimType::Text), + TypeInner::Reserved => IDLType::PrimT(PrimType::Reserved), + TypeInner::Empty => IDLType::PrimT(PrimType::Empty), + TypeInner::Var(id) => IDLType::VarT(id.to_string()), + TypeInner::Opt(t) => IDLType::OptT(Box::new(self.as_idl_type(t))), + TypeInner::Vec(t) => IDLType::VecT(Box::new(self.as_idl_type(t))), + TypeInner::Record(fields) => IDLType::RecordT(self.fields_to_idl_fields(fields)), + TypeInner::Variant(fields) => IDLType::VariantT(self.fields_to_idl_fields(fields)), + TypeInner::Func(func) => IDLType::FuncT(self.func_to_idl_func(func)), + TypeInner::Service(methods) => IDLType::ServT(self.methods_to_idl_methods(methods)), + TypeInner::Class(args, t) => IDLType::ClassT( + self.arg_types_to_idl_arg_types(args), + Box::new(self.as_idl_type(t)), + ), + TypeInner::Principal => IDLType::PrincipalT, + TypeInner::Knot(id) => { + let name = id.to_string(); + self.0 + .get(&name) + .unwrap_or_else(|| panic!("Knot type should already be in the env: {:?}", id)); + IDLType::VarT(name) + } + TypeInner::Unknown | TypeInner::Future => { + panic!("Unknown type: {:?}", ty) + } + } + } + + pub fn as_idl_type(&self, ty: &Type) -> IDLType { + self.inner_as_idl_type(ty.as_ref()) + } + + pub fn arg_types_to_idl_arg_types(&self, args: &[ArgType]) -> Vec { + args.iter() + .map(|arg| IDLArgType { + typ: self.as_idl_type(&arg.typ), + name: arg.name.clone(), + }) + .collect() + } + + pub fn func_to_idl_func(&self, func: &Function) -> FuncType { + FuncType { + modes: func.modes.clone(), + args: self.arg_types_to_idl_arg_types(&func.args), + rets: func.rets.iter().map(|arg| self.as_idl_type(arg)).collect(), + } + } + + pub fn field_to_idl_field(&self, field: &Field) -> TypeField { + TypeField { + label: field.id.as_ref().clone(), + typ: self.as_idl_type(&field.ty), + } + } + + fn fields_to_idl_fields(&self, fields: &[Field]) -> Vec { + fields + .iter() + .map(|field| self.field_to_idl_field(field)) + .collect() + } + + fn methods_to_idl_methods(&self, methods: &[(String, Type)]) -> Vec { + methods + .iter() + .map(|(id, t)| Binding { + id: id.clone(), + typ: self.as_idl_type(t), + }) + .collect() + } } impl std::fmt::Display for TypeEnv { diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index ed42c5332..5abf54374 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -7,11 +7,12 @@ use candid::{ ser::IDLBuilder, types::{ get_type, + internal::TypeContainer, syntax::{IDLType, PrimType, TypeField}, value::{IDLValue, IDLValueVisitor}, Label, Serializer, Type, TypeInner, }, - variant, CandidType, Decode, Deserialize, Encode, Int, + variant, CandidType, Decode, Deserialize, Encode, Int, TypeEnv, }; use serde::de::DeserializeOwned; @@ -88,48 +89,56 @@ fn any_val() { #[test] fn test_primitive() { + let env = TypeEnv::new(); + let bool_prim = get_type(&true); assert_eq!(bool_prim, TypeInner::Bool.into()); - assert_eq!(IDLType::from(bool_prim), IDLType::PrimT(PrimType::Bool)); + assert_eq!(env.as_idl_type(&bool_prim), IDLType::PrimT(PrimType::Bool)); let null_prim = get_type(&()); assert_eq!(null_prim, TypeInner::Null.into()); - assert_eq!(IDLType::from(null_prim), IDLType::PrimT(PrimType::Null)); + assert_eq!(env.as_idl_type(&null_prim), IDLType::PrimT(PrimType::Null)); let int_prim = get_type(&Box::new(42)); assert_eq!(int_prim, TypeInner::Int32.into()); - assert_eq!(IDLType::from(int_prim), IDLType::PrimT(PrimType::Int32)); + assert_eq!(env.as_idl_type(&int_prim), IDLType::PrimT(PrimType::Int32)); let int_prim = get_type(&Box::new(Int::from(42))); assert_eq!(int_prim, TypeInner::Int.into()); - assert_eq!(IDLType::from(int_prim), IDLType::PrimT(PrimType::Int)); + assert_eq!(env.as_idl_type(&int_prim), IDLType::PrimT(PrimType::Int)); let opt: Option<&str> = None; let opt_prim = get_type(&opt); assert_eq!(opt_prim, TypeInner::Opt(TypeInner::Text.into()).into()); assert_eq!( - IDLType::from(opt_prim), + env.as_idl_type(&opt_prim), IDLType::OptT(Box::new(IDLType::PrimT(PrimType::Text))) ); let vec_prim = get_type(&[0, 1, 2, 3]); assert_eq!(vec_prim, TypeInner::Vec(TypeInner::Int32.into()).into()); assert_eq!( - IDLType::from(vec_prim), + env.as_idl_type(&vec_prim), IDLType::VecT(Box::new(IDLType::PrimT(PrimType::Int32))) ); let nat_prim = get_type(&std::marker::PhantomData::); assert_eq!(nat_prim, TypeInner::Nat32.into()); - assert_eq!(IDLType::from(nat_prim), IDLType::PrimT(PrimType::Nat32)); + assert_eq!(env.as_idl_type(&nat_prim), IDLType::PrimT(PrimType::Nat32)); } #[test] fn test_struct() { + let mut type_container = TypeContainer::new(); + #[derive(Debug, CandidType)] struct Newtype(Int); assert_eq!(Newtype::ty(), TypeInner::Int.into()); - assert_eq!(IDLType::from(Newtype::ty()), IDLType::PrimT(PrimType::Int)); + type_container.add::(); + assert_eq!( + type_container.as_idl_type(&Newtype::ty()), + IDLType::PrimT(PrimType::Int) + ); #[derive(Debug, CandidType)] struct A { @@ -140,8 +149,9 @@ fn test_struct() { A::ty(), record! { foo: TypeInner::Int.into(); bar: TypeInner::Bool.into() } ); + type_container.add::(); assert_eq!( - IDLType::from(A::ty()), + type_container.as_idl_type(&A::ty()), IDLType::RecordT(vec![ TypeField { label: Label::Named("bar".to_string()), @@ -164,8 +174,9 @@ fn test_struct() { get_type(&res), record! { g1: TypeInner::Int32.into(); g2: TypeInner::Bool.into() } ); + type_container.add::>(); assert_eq!( - IDLType::from(get_type(&res)), + type_container.as_idl_type(&get_type(&res)), IDLType::RecordT(vec![ TypeField { label: Label::Named("g1".to_string()), @@ -187,8 +198,9 @@ fn test_struct() { List::ty(), record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::()).into()).into() } ); + type_container.add::(); assert_eq!( - IDLType::from(List::ty()), + type_container.as_idl_type(&List::ty()), IDLType::RecordT(vec![ TypeField { label: Label::Named("head".to_string()), @@ -196,9 +208,7 @@ fn test_struct() { }, TypeField { label: Label::Named("tail".to_string()), - typ: IDLType::OptT(Box::new( - IDLType::KnotT(candid::types::TypeId::of::()) - )), + typ: IDLType::OptT(Box::new(IDLType::VarT("List".to_string()))), }, ]) ); @@ -212,8 +222,9 @@ fn test_struct() { GenericList::::ty(), record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::>()).into()).into() } ); + type_container.add::>(); assert_eq!( - IDLType::from(GenericList::::ty()), + type_container.as_idl_type(&GenericList::::ty()), IDLType::RecordT(vec![ TypeField { label: Label::Named("head".to_string()), @@ -221,9 +232,7 @@ fn test_struct() { }, TypeField { label: Label::Named("tail".to_string()), - typ: IDLType::OptT(Box::new(IDLType::KnotT(candid::types::TypeId::of::< - GenericList, - >()))), + typ: IDLType::OptT(Box::new(IDLType::VarT("GenericList".to_string()))), }, ]) ); @@ -234,8 +243,9 @@ fn test_struct() { Wrap::ty(), record! { head: TypeInner::Int32.into(); tail: TypeInner::Opt(TypeInner::Knot(candid::types::TypeId::of::>()).into()).into() } ); + type_container.add::(); assert_eq!( - IDLType::from(Wrap::ty()), + type_container.as_idl_type(&Wrap::ty()), IDLType::RecordT(vec![ TypeField { label: Label::Named("head".to_string()), @@ -243,9 +253,7 @@ fn test_struct() { }, TypeField { label: Label::Named("tail".to_string()), - typ: IDLType::OptT(Box::new(IDLType::KnotT(candid::types::TypeId::of::< - GenericList, - >()))), + typ: IDLType::OptT(Box::new(IDLType::VarT("GenericList".to_string()))), }, ]) ); @@ -253,6 +261,8 @@ fn test_struct() { #[test] fn test_variant() { + let mut type_container = TypeContainer::new(); + #[allow(dead_code)] #[derive(Debug, CandidType)] enum E { @@ -272,8 +282,9 @@ fn test_variant() { Newtype: TypeInner::Bool.into(); } ); + type_container.add::(); assert_eq!( - IDLType::from(get_type(&v)), + type_container.as_idl_type(&get_type(&v)), IDLType::VariantT(vec![ TypeField { label: Label::Named("Bar".to_string()), @@ -390,13 +401,7 @@ fn test_func() { fn init(_: List) {} candid::export_service!(); - let expected = r#"type List = record { head : nat8; tail : opt List }; -type Result = variant { Ok : List; Err : empty }; -type NamedStruct = record { a : nat16; b : int32 }; -type List_1 = record { head : int8; tail : opt List_1 }; -type Box = record { head : int8; tail : opt Box }; -type Wrap = record { head : int8; tail : opt Box }; -type A = variant { + let expected = r#"type A = variant { A1 : record { List_1; Wrap; Wrap }; A2 : record { text; principal }; A3 : int; @@ -405,8 +410,14 @@ type A = variant { A6 : NamedStruct; A7 : record { b : int32; c : nat16 }; }; -type Result_1 = variant { Ok : record { record { A }; A }; Err : text }; +type Box = record { head : int8; tail : opt Box }; +type List = record { head : nat8; tail : opt List }; +type List_1 = record { head : int8; tail : opt List_1 }; type List_2 = record { head : int; tail : opt List_2 }; +type NamedStruct = record { a : nat16; b : int32 }; +type Result = variant { Ok : List; Err : empty }; +type Result_1 = variant { Ok : record { record { A }; A }; Err : text }; +type Wrap = record { head : int8; tail : opt Box }; service : (List_2) -> { id_struct : (record { List }) -> (Result) query; id_struct_composite : (record { List }) -> (Result) composite_query; diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 7c886563e..60133a391 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -122,6 +122,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { }); let service = quote! { use #candid::types::{CandidType, Function, Type, ArgType, TypeInner}; + use #candid::types::syntax::{Binding, IDLMergedProg}; let mut service = Vec::<(String, Type)>::new(); let mut env = #candid::types::internal::TypeContainer::new(); #(#gen_tys)* @@ -131,16 +132,24 @@ pub(crate) fn export_service(path: Option) -> TokenStream { let actor = if let Some(init) = init { quote! { #init - let actor = Some(TypeInner::Class(init_args, ty).into()); + let actor = TypeInner::Class(init_args, ty).into(); } } else { - quote! { let actor = Some(ty); } + quote! { let actor = ty; } }; let res = quote! { fn __export_service() -> String { #service #actor - let result = #candid::pretty::candid::compile(&env.env, &actor); + + let bindings = env.env.0.iter().map(|(id, t)| Binding { + id: id.clone(), + typ: env.as_idl_type(t), + }).collect::>(); + let mut idl_merged_prog = IDLMergedProg::from(bindings); + idl_merged_prog.set_actor(Some(env.as_idl_type(&actor))); + + let result = #candid::pretty::candid::compile(&idl_merged_prog); format!("{}", result) } }; diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 9f11f7089..69b22f7d2 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -374,7 +374,7 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option, I res = merge_actor(&env, &res, &actor, "")?; } - idl_merged_prog.set_actor(res.clone().map(Into::into)); + idl_merged_prog.set_actor(res.clone().map(|t| te.as_idl_type(&t))); Ok((te, res, idl_merged_prog)) } diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 5136b9083..0b026a38a 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -5,7 +5,7 @@ use candid_parser::{ syntax::{IDLType, IDLTypes}, Type, }, - types::syntax::IDLEnv, + types::syntax::IDLMergedProg, }; use candid_parser::{ configs::Configs, parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, @@ -136,19 +136,21 @@ impl TypeAnnotation { fn get_types( &self, mode: Mode, - ) -> candid_parser::Result<(TypeEnv, IDLEnv, Vec, Vec)> { - let (env, idl_env, actor) = if let Some(ref file) = self.defs { + ) -> candid_parser::Result<(TypeEnv, Vec, IDLMergedProg, Vec)> { + let (env, _, idl_prog) = if let Some(ref file) = self.defs { pretty_check_file(file)? } else { - (TypeEnv::new(), IDLEnv::new(), None) + (TypeEnv::new(), None, IDLMergedProg::new()) }; let idl_types = match (&self.tys, &self.method) { (None, None) => return Err(Error::msg("no type annotations")), (Some(tys), None) => tys.args.clone(), (None, Some(meth)) => { - let actor = actor + let actor = idl_prog + .actor + .as_ref() .ok_or_else(|| Error::msg("Cannot use --method with a non-service did file"))?; - let func = idl_env.get_method(&actor, meth).map_err(Error::msg)?; + let func = idl_prog.get_method(actor, meth).map_err(Error::msg)?; match mode { Mode::Encode => func.args.iter().map(|arg| arg.typ.clone()).collect(), Mode::Decode => func.rets.clone(), @@ -160,7 +162,7 @@ impl TypeAnnotation { for ty in idl_types.iter() { types.push(ast_to_type(&env, ty)?); } - Ok((env, idl_env, types, idl_types)) + Ok((env, types, idl_prog, idl_types)) } } @@ -194,13 +196,11 @@ fn main() -> Result<()> { previous, strict, } => { - let (mut env, _, opt_t1) = pretty_check_file(&input)?; + let (mut env, opt_t1, _) = pretty_check_file(&input)?; if let Some(previous) = previous { - let (env2, _, opt_t2) = pretty_check_file(&previous)?; + let (env2, opt_t2, _) = pretty_check_file(&previous)?; match (opt_t1, opt_t2) { - (Some(idl_t1), Some(idl_t2)) => { - let t1 = ast_to_type(&env, &idl_t1)?; - let t2 = ast_to_type(&env2, &idl_t2)?; + (Some(t1), Some(t2)) => { let mut gamma = HashSet::new(); let t2 = env.merge_type(env2, t2); if strict { @@ -232,7 +232,7 @@ fn main() -> Result<()> { methods: _, } => { let configs = load_config(&config)?; - let (_, idl_env, _) = pretty_check_file(&input)?; + let (_, _, idl_env) = pretty_check_file(&input)?; let content = match target.as_str() { "js" => candid_parser::bindings::javascript::compile(&idl_env), "ts" => candid_parser::bindings::typescript::compile(&idl_env), @@ -286,7 +286,7 @@ fn main() -> Result<()> { } Command::Assist { annotate } => { use candid_parser::assist::{input_args, Context}; - let (env, _, types, _) = annotate.get_types(Mode::Encode)?; + let (env, types, _, _) = annotate.get_types(Mode::Encode)?; let ctx = Context::new(env.clone()); let args = input_args(&ctx, &types)?; println!("{args}"); @@ -308,7 +308,7 @@ fn main() -> Result<()> { let bytes = if annotate.is_empty() { args.to_bytes()? } else { - let (env, _, types, _) = annotate.get_types(Mode::Encode)?; + let (env, types, _, _) = annotate.get_types(Mode::Encode)?; args.to_bytes_with_types(&env, &types)? }; let hex = match format.as_str() { @@ -349,7 +349,7 @@ fn main() -> Result<()> { let value = if annotate.is_empty() { IDLArgs::from_bytes(&bytes)? } else { - let (env, _, types, _) = annotate.get_types(Mode::Decode)?; + let (env, types, _, _) = annotate.get_types(Mode::Decode)?; IDLArgs::from_bytes_with_types(&bytes, &env, &types)? }; println!("{value}"); @@ -362,7 +362,7 @@ fn main() -> Result<()> { } => { use candid_parser::configs::{Scope, ScopePos}; use rand::Rng; - let (_, idl_env, _, idl_types) = if args.is_some() { + let (_, _, idl_prog, idl_types) = if args.is_some() { annotate.get_types(Mode::Decode)? } else { annotate.get_types(Mode::Encode)? @@ -370,7 +370,7 @@ fn main() -> Result<()> { let config = load_config(&config)?; // TODO figure out how many bytes of entropy we need let seed: Vec = if let Some(ref args) = args { - let (env, _, types, _) = annotate.get_types(Mode::Encode)?; + let (env, types, _, _) = annotate.get_types(Mode::Encode)?; let bytes = args.to_bytes_with_types(&env, &types)?; bytes.into_iter().rev().cycle().take(2048).collect() } else { @@ -385,7 +385,7 @@ fn main() -> Result<()> { }); Scope { position, method } }); - let args = candid_parser::random::any(&seed, config, &idl_env, &idl_types, &scope)?; + let args = candid_parser::random::any(&seed, config, &idl_prog, &idl_types, &scope)?; match lang.as_str() { "did" => println!("{args}"), "js" => println!( From fe831689f1ddd5a0455bcdb586f30e2cedccca3a Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 24 Jun 2025 13:58:46 +0200 Subject: [PATCH 27/34] fix: remove unused `get_id` function --- rust/candid/src/types/internal.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 931da5cec..a833ee157 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -746,10 +746,6 @@ pub(crate) fn env_id(id: TypeId, t: Type) { }); } -pub fn get_id(t: &Type) -> TypeId { - ID.with(|n| n.borrow().get(t).cloned().unwrap()) -} - pub fn get_type(_v: &T) -> Type where T: CandidType, From f9e1e8efcdc02b365ebd42153eb835baca99c36d Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 24 Jun 2025 14:05:17 +0200 Subject: [PATCH 28/34] chore: update changelog and remove unused code --- CHANGELOG.md | 16 +++++++--------- rust/candid/src/types/syntax.rs | 14 -------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5d8bb3d..bbe91256a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,9 @@ - `Binding` - `IDLProg` - `IDLInitArgs` - + `IDLType::KnotT` is now a valid type. - + The `IDLEnv` struct has been added to support the parsing and bindings generation, to avoid using the `TypeEnv`. - + The IDL types can now be converted to `Type` and vice versa, using the `From` trait. + + The `IDLMergedProg` struct has been added to support the parsing and bindings generation, to avoid using the `TypeEnv`. + + The IDL types can now be converted to `Type` and using the `From` trait. + + The `TypeEnv` struct now has a `as_idl_type` method to convert `Type` to `IDLType`. ### candid_parser @@ -46,12 +46,10 @@ You must now use the `parse_idl_prog`, `parse_idl_init_args`, `parse_idl_type` and `parse_idl_types` functions to parse these types, respectively. + `pretty_parse` doesn't work anymore with the `IDLProg` and `IDLTypes` types. Use `pretty_parse_idl_prog` and `pretty_parse_idl_types` instead. + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. - + The bindings generators now use the IDL env and types. - + `check_file` and `pretty_check_file` now return a `(TypeEnv, IDLEnv, Option)` tuple, instead of `(TypeEnv, Option)`. Same for the `CandidSource.load` method. - + `check_prog` now returns a `Result>` instead of `Result>`. - + `typing::check_init_args` now accepts the additional `&mut IDLEnv` parameter. - + `utils::get_metadata` now accepts only an `&IDLEnv` parameter. - + The `chase_type`, `chase_actor`, `chase_def_use`, `chase_types` and `infer_rec` functions of the `bindings::analysis` module now accept an IDL env and types as parameters. + + The bindings generators now use the IDL merged prog and types. + + `check_file` and `pretty_check_file` now return a `(TypeEnv, Option, IDLMergedProg)` tuple, instead of `(TypeEnv, Option)`. Same for the `CandidSource.load` method. + + `utils::get_metadata` now accepts only an `&IDLMergedProg` parameter. + + The `chase_type`, `chase_actor`, `chase_def_use`, `chase_types` and `infer_rec` functions of the `bindings::analysis` module now accept an IDL merged prog and types as parameters. * Non-breaking changes: + Supports parsing the arguments' names for `func` and `service` (init args). diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 933b85524..98d25e61a 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -213,20 +213,6 @@ pub struct IDLProg { pub actor: Option, } -impl IDLProg { - pub fn new() -> Self { - Self::default() - } - - pub fn add_binding(&mut self, binding: Binding) { - self.decs.push(Dec::TypD(binding)); - } - - pub fn set_actor(&mut self, actor: Option) { - self.actor = actor; - } -} - #[derive(Debug)] pub struct IDLInitArgs { pub decs: Vec, From 09b3b4434ef26b14a041f655bb4707d55bcdb313 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Tue, 24 Jun 2025 14:12:50 +0200 Subject: [PATCH 29/34] perf: fix bench tests --- rust/bench/bench.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 3f84e1e26..0e727fcc0 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -1,6 +1,5 @@ use canbench_rs::{bench, bench_fn, bench_scope, BenchResult}; use candid::{CandidType, Decode, DecoderConfig, Deserialize, Encode, Int, Nat}; -use candid_parser::typing::ast_to_type; use std::collections::BTreeMap; #[allow(clippy::all)] @@ -213,9 +212,9 @@ fn nns() -> BenchResult { "#; bench_fn(|| { let _p = bench_scope("0. Parsing"); - let (env, _, serv) = nns_did.load().unwrap(); + let (env, serv, _) = nns_did.load().unwrap(); let args = candid_parser::parse_idl_args(motion_proposal).unwrap(); - let serv = ast_to_type(&env, &serv.unwrap()).unwrap(); + let serv = serv.unwrap(); let method = &env.get_method(&serv, "manage_neuron").unwrap(); let arg_tys = method .args From b93c4d8c8682d30928c0f4c47f9de4414568f4f9 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 25 Jun 2025 16:30:02 +0200 Subject: [PATCH 30/34] style: remove unused import --- tools/didc/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index 872c84e5c..89959ddd9 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -6,8 +6,7 @@ use candid_parser::candid::types::{ }; use candid_parser::{ configs::Configs, parse_idl_args, parse_idl_type, parse_idl_value, pretty_check_file, - pretty_parse, pretty_parse_idl_types, pretty_wrap, typing::ast_to_type, Error, IDLArgs, - IDLValue, TypeEnv, + pretty_parse_idl_types, pretty_wrap, typing::ast_to_type, Error, IDLArgs, IDLValue, TypeEnv, }; use clap::Parser; use console::style; From c70a084ac79bd6a7856f9c95bad6d17d279fc8f4 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 25 Jun 2025 18:57:05 +0200 Subject: [PATCH 31/34] test: update goldenfiles --- rust/candid_parser/tests/assets/ok/inline_methods.d.ts | 2 +- rust/candid_parser/tests/assets/ok/inline_methods.did | 2 +- rust/candid_parser/tests/assets/ok/inline_methods.mo | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index 687fa6900..5be20d45f 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts +++ b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts @@ -4,13 +4,13 @@ import type { IDL } from '@dfinity/candid'; export type Fn = ActorMethod<[bigint], bigint>; export type Gn = Fn; +export interface RInline { 'x' : bigint, 'fn' : [Principal, string] } export interface R { 'x' : bigint, 'fn' : [Principal, string], 'gn' : [Principal, string], 'nested' : { 'fn' : [Principal, string] }, } -export interface RInline { 'x' : bigint, 'fn' : [Principal, string] } export interface _SERVICE { 'add_two' : ActorMethod<[bigint], bigint>, 'fn' : Fn, diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.did b/rust/candid_parser/tests/assets/ok/inline_methods.did index 6e568643f..7a0589bd5 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.did +++ b/rust/candid_parser/tests/assets/ok/inline_methods.did @@ -1,7 +1,7 @@ type Fn = func (nat) -> (nat) query; type Gn = Fn; -type R = record { x : nat; fn : Fn; gn : Gn; nested : record { fn : Gn } }; type RInline = record { x : nat; fn : func (nat) -> (nat) query }; +type R = record { x : nat; fn : Fn; gn : Gn; nested : record { fn : Gn } }; service : { add_two : (nat) -> (nat); fn : Fn; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.mo b/rust/candid_parser/tests/assets/ok/inline_methods.mo index b1c8b0168..64cb85e21 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.mo +++ b/rust/candid_parser/tests/assets/ok/inline_methods.mo @@ -4,8 +4,8 @@ module { public type Fn = shared query Nat -> async Nat; public type Gn = Fn; - public type R = { x : Nat; fn : Fn; gn : Gn; nested : { fn : Gn } }; public type RInline = { x : Nat; fn : shared query Nat -> async Nat }; + public type R = { x : Nat; fn : Fn; gn : Gn; nested : { fn : Gn } }; public type Self = actor { add_two : shared Nat -> async Nat; fn : Fn; From 2f9fda61157d6d43d257c05d3a7408c8273f2e9b Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 25 Jun 2025 20:21:44 +0200 Subject: [PATCH 32/34] feat: future and unknown variants in idl type --- rust/candid/src/pretty/candid.rs | 2 + rust/candid/src/types/syntax.rs | 6 ++ rust/candid/src/types/type_env.rs | 5 +- rust/candid_parser/src/bindings/javascript.rs | 2 +- rust/candid_parser/src/bindings/motoko.rs | 1 + rust/candid_parser/src/bindings/rust.rs | 8 +- rust/candid_parser/src/bindings/typescript.rs | 2 +- rust/candid_parser/src/configs.rs | 1 + rust/candid_parser/src/lib.rs | 6 +- rust/candid_parser/src/test.rs | 89 ------------------- rust/candid_parser/src/typing.rs | 2 + 11 files changed, 23 insertions(+), 101 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 62cc4f60e..c231d4c06 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -125,6 +125,8 @@ pub fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } + FutureT => str("future"), + UnknownT => str("unknown"), } } diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 98d25e61a..28b15dae2 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -14,6 +14,10 @@ pub enum IDLType { ServT(Vec), ClassT(Vec, Box), PrincipalT, + /// Used in test files. + FutureT, + /// Used in test files. + UnknownT, } impl IDLType { @@ -79,6 +83,8 @@ impl From for Type { TypeInner::Class(args.into_iter().map(|t| t.into()).collect(), (*t).into()) } IDLType::PrincipalT => TypeInner::Principal, + IDLType::FutureT => TypeInner::Future, + IDLType::UnknownT => TypeInner::Unknown, } .into() } diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 5dd1d58c6..b39d8543e 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -204,9 +204,8 @@ impl TypeEnv { .unwrap_or_else(|| panic!("Knot type should already be in the env: {:?}", id)); IDLType::VarT(name) } - TypeInner::Unknown | TypeInner::Future => { - panic!("Unknown type: {:?}", ty) - } + TypeInner::Future => IDLType::FutureT, + TypeInner::Unknown => IDLType::UnknownT, } } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index bc72b0d4a..9f4cc197a 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -136,7 +136,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), FuncT(ref func) => str("IDL.Func").append(pp_function(func)), ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), - ClassT(_, _) => unreachable!(), + ClassT(_, _) | FutureT | UnknownT => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index a41dc387e..e9a3e6ad4 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -148,6 +148,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } + FutureT | UnknownT => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 02e627d97..002e93bd2 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -281,9 +281,11 @@ fn test_{test_name}() {{ let body = ok.append(", ").append(err); RcDoc::text(result).append(enclose("<", body, ">")) } - FuncT(_) => unreachable!(), // not possible after rewriting - ServT(_) => unreachable!(), // not possible after rewriting - ClassT(_, _) => unreachable!(), + FuncT(_) // not possible after rewriting + | ServT(_) // not possible after rewriting + | ClassT(_, _) + | FutureT + | UnknownT => unreachable!(), } }; 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 a8b825345..f9feeb324 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -102,7 +102,7 @@ fn pp_ty<'a>(prog: &'a IDLMergedProg, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a } FuncT(_) => pp_inline_func(), ServT(_) => pp_inline_service(), - ClassT(_, _) => unreachable!(), + ClassT(_, _) | FutureT | UnknownT => unreachable!(), } } diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 0378c2ea2..340846c9d 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -408,6 +408,7 @@ fn path_name(t: &IDLType) -> String { IDLType::FuncT(_) => "func", IDLType::ServT(_) => "service", IDLType::ClassT(..) => "func:init", + IDLType::FutureT | IDLType::UnknownT => unreachable!(), } .to_string() } diff --git a/rust/candid_parser/src/lib.rs b/rust/candid_parser/src/lib.rs index 116bf3b65..ba0596dd7 100644 --- a/rust/candid_parser/src/lib.rs +++ b/rust/candid_parser/src/lib.rs @@ -47,7 +47,7 @@ //! ``` //! # fn f() -> anyhow::Result<()> { //! use candid::{TypeEnv, types::{Type, TypeInner}}; -//! use candid_parser::{check_prog, parse_idl_prog, typing::ast_to_type}; +//! use candid_parser::{check_prog, parse_idl_prog}; //! let did_file = r#" //! type List = opt record { head: int; tail: List }; //! type byte = nat8; @@ -66,7 +66,6 @@ //! // Note that file import is ignored by check_prog. //! let mut env = TypeEnv::new(); //! let actor = check_prog(&mut env, &ast)?.unwrap(); -//! let actor = ast_to_type(&env, &actor)?; //! //! let method = env.get_method(&actor, "g").unwrap(); //! assert_eq!(method.is_query(), true); @@ -87,7 +86,7 @@ //! use candid::{IDLArgs, types::value::IDLValue}; //! use candid_parser::parse_idl_args; //! # use candid::TypeEnv; -//! # use candid_parser::{check_prog, parse_idl_prog, typing::ast_to_type}; +//! # use candid_parser::{check_prog, parse_idl_prog}; //! # let did_file = r#" //! # type List = opt record { head: int; tail: List }; //! # type byte = nat8; @@ -99,7 +98,6 @@ //! # let ast = parse_idl_prog(did_file)?; //! # let mut env = TypeEnv::new(); //! # let actor = check_prog(&mut env, &ast)?.unwrap(); -//! # let actor = ast_to_type(&env, &actor)?; //! // Get method type f : (byte, int, nat, int8) -> (List) //! let method = env.get_method(&actor, "f").unwrap(); //! let args = parse_idl_args("(42, 42, 42, 42)")?; diff --git a/rust/candid_parser/src/test.rs b/rust/candid_parser/src/test.rs index c5d9d9391..974c4768e 100644 --- a/rust/candid_parser/src/test.rs +++ b/rust/candid_parser/src/test.rs @@ -27,19 +27,6 @@ pub enum Input { Blob(Vec), } -/// Generate assertions for host value -pub struct HostTest { - pub desc: String, - pub asserts: Vec, -} -pub enum HostAssert { - // The encoded bytes is not unique - Encode(IDLArgs, Vec<(IDLType, Type)>, bool, Vec), - NotEncode(IDLArgs, Vec<(IDLType, Type)>), - Decode(Vec, Vec<(IDLType, Type)>, bool, IDLArgs), - NotDecode(Vec, Vec<(IDLType, Type)>), -} - impl Assert { pub fn desc(&self) -> String { match &self.desc { @@ -82,74 +69,6 @@ impl std::str::FromStr for Test { } } -impl HostTest { - pub fn from_assert(assert: &Assert, env: &TypeEnv, types: &[(IDLType, Type)]) -> Self { - use HostAssert::*; - let types = types.to_vec(); - let mut asserts = Vec::new(); - match &assert.left { - Input::Text(s) => { - // Without type annotation, numbers are all of type int. - // Assertion may not pass. - let parsed = crate::parse_idl_args(s); - if parsed.is_err() { - let desc = format!("(skip) {}", assert.desc()); - return HostTest { desc, asserts }; - } - let parsed = parsed.unwrap(); - if !assert.pass && assert.right.is_none() { - asserts.push(NotEncode(parsed, types)); - } else { - let bytes = parsed.to_bytes_with_types(env, &to_types(&types)).unwrap(); - asserts.push(Encode(parsed.clone(), types.clone(), true, bytes.clone())); - // round tripping - let vals = parsed.annotate_types(true, env, &to_types(&types)).unwrap(); - asserts.push(Decode(bytes, types, true, vals)); - } - let desc = format!("(encode?) {}", assert.desc()); - HostTest { desc, asserts } - } - Input::Blob(bytes) => { - let bytes = bytes.to_vec(); - if !assert.pass && assert.right.is_none() { - asserts.push(NotDecode(bytes, types)); - } else { - let mut config = DecoderConfig::new(); - config.set_decoding_quota(DECODING_COST); - let args = IDLArgs::from_bytes_with_types_with_config( - &bytes, - env, - &to_types(&types), - &config, - ) - .unwrap(); - asserts.push(Decode(bytes.clone(), types.clone(), true, args)); - // round tripping - // asserts.push(Encode(args, types.clone(), true, bytes.clone())); - if let Some(right) = &assert.right { - let expected = right.parse(env, &to_types(&types)).unwrap(); - if let Input::Blob(blob) = right { - asserts.push(Decode( - blob.to_vec(), - types.clone(), - true, - expected.clone(), - )); - } - if !assert.pass { - asserts.push(Decode(bytes, types, assert.pass, expected)); - } - } - } - HostTest { - desc: assert.desc(), - asserts, - } - } - } - } -} - pub fn check(test: Test) -> Result<()> { let mut env = TypeEnv::new(); let prog = IDLProg { @@ -202,11 +121,3 @@ pub fn check(test: Test) -> Result<()> { ))) } } - -pub fn to_idl_types(types: &[(IDLType, Type)]) -> Vec { - types.iter().map(|(idl_typ, _)| idl_typ.clone()).collect() -} - -fn to_types(types: &[(IDLType, Type)]) -> Vec { - types.iter().map(|(_, ty)| ty.clone()).collect() -} diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 69b22f7d2..84a0d616e 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -100,6 +100,8 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { Ok(TypeInner::Service(ms).into()) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), + IDLType::FutureT => Err(Error::msg("future not supported")), + IDLType::UnknownT => Err(Error::msg("unknown type not supported")), } } From 8f45f2154c2b83a5eb052b1a35b646b4e9dbf753 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Thu, 26 Jun 2025 15:15:37 +0200 Subject: [PATCH 33/34] refactor: remove redundant code --- rust/candid_parser/src/typing.rs | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index 84a0d616e..b30e23cde 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -6,7 +6,7 @@ use candid::types::{ ArgType, Field, Function, Type, TypeEnv, TypeInner, }; use candid::utils::check_unique; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; pub struct Env<'a> { @@ -155,29 +155,6 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> { Ok(()) } -fn check_cycle(env: &TypeEnv) -> Result<()> { - fn has_cycle<'a>(seen: &mut BTreeSet<&'a str>, env: &'a TypeEnv, t: &'a Type) -> Result { - match t.as_ref() { - TypeInner::Var(id) => { - if seen.insert(id) { - let ty = env.find_type(id)?; - has_cycle(seen, env, ty) - } else { - Ok(true) - } - } - _ => Ok(false), - } - } - for (id, ty) in env.0.iter() { - let mut seen = BTreeSet::new(); - if has_cycle(&mut seen, env, ty)? { - return Err(Error::msg(format!("{id} has cyclic type definition"))); - } - } - Ok(()) -} - fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, typ: _ }) = dec { @@ -189,7 +166,7 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { } env.pre = true; check_defs(env, decs)?; - check_cycle(env.te)?; + env.te.check_cycle()?; env.pre = false; check_defs(env, decs)?; Ok(()) @@ -376,7 +353,7 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option, I res = merge_actor(&env, &res, &actor, "")?; } - idl_merged_prog.set_actor(res.clone().map(|t| te.as_idl_type(&t))); + idl_merged_prog.set_actor(res.clone().map(|t| env.te.as_idl_type(&t))); Ok((te, res, idl_merged_prog)) } From dbb1dd4d3ffba544afbd76c1a35b80432ca64284 Mon Sep 17 00:00:00 2001 From: ilbertt Date: Mon, 30 Jun 2025 21:24:01 +0200 Subject: [PATCH 34/34] fix: remove future and unknown from idl types --- rust/candid/src/pretty/candid.rs | 2 -- rust/candid/src/types/internal.rs | 25 ++++++++++--------- rust/candid/src/types/syntax.rs | 6 ----- rust/candid/src/types/type_env.rs | 10 ++++++-- rust/candid_parser/src/bindings/javascript.rs | 2 +- rust/candid_parser/src/bindings/motoko.rs | 1 - rust/candid_parser/src/bindings/rust.rs | 4 +-- rust/candid_parser/src/bindings/typescript.rs | 2 +- rust/candid_parser/src/configs.rs | 1 - rust/candid_parser/src/typing.rs | 2 -- 10 files changed, 24 insertions(+), 31 deletions(-) diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index c231d4c06..62cc4f60e 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -125,8 +125,6 @@ pub fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } - FutureT => str("future"), - UnknownT => str("unknown"), } } diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index a833ee157..af0345ac4 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -332,23 +332,24 @@ impl Type { #[cfg(feature = "printer")] impl fmt::Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let env = crate::TypeEnv::new(); - write!( - f, - "{}", - crate::pretty::candid::pp_ty(&env.as_idl_type(self)).pretty(80), - ) + write!(f, "{}", self.as_ref()) } } #[cfg(feature = "printer")] impl fmt::Display for TypeInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let env = crate::TypeEnv::new(); - write!( - f, - "{}", - crate::pretty::candid::pp_ty(&env.inner_as_idl_type(self)).pretty(80), - ) + match self { + TypeInner::Future => write!(f, "future"), + TypeInner::Unknown => write!(f, "unknown"), + _ => { + let env = crate::TypeEnv::new(); + write!( + f, + "{}", + crate::pretty::candid::pp_ty(&env.inner_as_idl_type(self)).pretty(80), + ) + } + } } } #[cfg(not(feature = "printer"))] diff --git a/rust/candid/src/types/syntax.rs b/rust/candid/src/types/syntax.rs index 28b15dae2..98d25e61a 100644 --- a/rust/candid/src/types/syntax.rs +++ b/rust/candid/src/types/syntax.rs @@ -14,10 +14,6 @@ pub enum IDLType { ServT(Vec), ClassT(Vec, Box), PrincipalT, - /// Used in test files. - FutureT, - /// Used in test files. - UnknownT, } impl IDLType { @@ -83,8 +79,6 @@ impl From for Type { TypeInner::Class(args.into_iter().map(|t| t.into()).collect(), (*t).into()) } IDLType::PrincipalT => TypeInner::Principal, - IDLType::FutureT => TypeInner::Future, - IDLType::UnknownT => TypeInner::Unknown, } .into() } diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index b39d8543e..0235e473c 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -166,6 +166,13 @@ impl TypeEnv { Ok(()) } + /// # Panics + /// + /// Panics if the type is [TypeInner::Future] or [TypeInner::Unknown]. + /// You must handle those cases before calling this function. + /// + /// Panics if the id referenced by the [TypeInner::Knot] was not inserted + /// into the env before calling this function. pub fn inner_as_idl_type(&self, ty: &TypeInner) -> IDLType { match ty { TypeInner::Null => IDLType::PrimT(PrimType::Null), @@ -204,8 +211,7 @@ impl TypeEnv { .unwrap_or_else(|| panic!("Knot type should already be in the env: {:?}", id)); IDLType::VarT(name) } - TypeInner::Future => IDLType::FutureT, - TypeInner::Unknown => IDLType::UnknownT, + TypeInner::Future | TypeInner::Unknown => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/javascript.rs b/rust/candid_parser/src/bindings/javascript.rs index 9f4cc197a..bc72b0d4a 100644 --- a/rust/candid_parser/src/bindings/javascript.rs +++ b/rust/candid_parser/src/bindings/javascript.rs @@ -136,7 +136,7 @@ fn pp_ty(ty: &IDLType) -> RcDoc { VariantT(ref fs) => str("IDL.Variant").append(pp_fields(fs)), FuncT(ref func) => str("IDL.Func").append(pp_function(func)), ServT(ref serv) => str("IDL.Service").append(pp_service(serv)), - ClassT(_, _) | FutureT | UnknownT => unreachable!(), + ClassT(_, _) => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index e9a3e6ad4..a41dc387e 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -148,7 +148,6 @@ fn pp_ty(ty: &IDLType) -> RcDoc { _ => unreachable!(), } } - FutureT | UnknownT => unreachable!(), } } diff --git a/rust/candid_parser/src/bindings/rust.rs b/rust/candid_parser/src/bindings/rust.rs index 002e93bd2..eb12def6e 100644 --- a/rust/candid_parser/src/bindings/rust.rs +++ b/rust/candid_parser/src/bindings/rust.rs @@ -283,9 +283,7 @@ fn test_{test_name}() {{ } FuncT(_) // not possible after rewriting | ServT(_) // not possible after rewriting - | ClassT(_, _) - | FutureT - | UnknownT => unreachable!(), + | ClassT(_, _) => unreachable!(), } }; 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 f9feeb324..a8b825345 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -102,7 +102,7 @@ fn pp_ty<'a>(prog: &'a IDLMergedProg, ty: &'a IDLType, is_ref: bool) -> RcDoc<'a } FuncT(_) => pp_inline_func(), ServT(_) => pp_inline_service(), - ClassT(_, _) | FutureT | UnknownT => unreachable!(), + ClassT(_, _) => unreachable!(), } } diff --git a/rust/candid_parser/src/configs.rs b/rust/candid_parser/src/configs.rs index 340846c9d..0378c2ea2 100644 --- a/rust/candid_parser/src/configs.rs +++ b/rust/candid_parser/src/configs.rs @@ -408,7 +408,6 @@ fn path_name(t: &IDLType) -> String { IDLType::FuncT(_) => "func", IDLType::ServT(_) => "service", IDLType::ClassT(..) => "func:init", - IDLType::FutureT | IDLType::UnknownT => unreachable!(), } .to_string() } diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index b30e23cde..7f9f609c6 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -100,8 +100,6 @@ pub fn check_type(env: &Env, t: &IDLType) -> Result { Ok(TypeInner::Service(ms).into()) } IDLType::ClassT(_, _) => Err(Error::msg("service constructor not supported")), - IDLType::FutureT => Err(Error::msg("future not supported")), - IDLType::UnknownT => Err(Error::msg("unknown type not supported")), } }