Skip to content

Commit

Permalink
Misc (#466)
Browse files Browse the repository at this point in the history
* add merge_init_args function merge candid:args and candid:service metadata

* fix

* record and variant macro
  • Loading branch information
chenyan-dfinity authored Sep 13, 2023
1 parent dbee878 commit a1eb218
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 35 deletions.
6 changes: 5 additions & 1 deletion rust/candid/src/parser/grammar.lalrpop
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::types::value::{IDLField, IDLValue, IDLArgs, VariantValue};
use crate::types::{TypeEnv, FuncMode};
use crate::utils::check_unique;
use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes};
use super::types::{IDLType, PrimType, TypeField, FuncType, Binding, Dec, IDLProg, IDLTypes, IDLInitArgs};
use super::test::{Assert, Input, Test};
use super::token::{Token, error2, LexicalError, Span};
use crate::{Principal, types::Label};
Expand Down Expand Up @@ -272,6 +272,10 @@ pub IDLProg: IDLProg = {
<decs:SepBy<Def, ";">> <actor:MainActor?> => IDLProg { decs, actor }
}

pub IDLInitArgs: IDLInitArgs = {
<decs:SepBy<Def, ";">> <args:TupTyp> => IDLInitArgs { decs, args }
}

// Test file

Input: Input = {
Expand Down
13 changes: 13 additions & 0 deletions rust/candid/src/parser/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,26 @@ pub struct IDLProg {
pub actor: Option<IDLType>,
}

#[derive(Debug)]
pub struct IDLInitArgs {
pub decs: Vec<Dec>,
pub args: Vec<IDLType>,
}

impl std::str::FromStr for IDLProg {
type Err = crate::Error;
fn from_str(str: &str) -> Result<Self> {
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<Self> {
let lexer = super::token::Tokenizer::new(str);
Ok(super::grammar::IDLInitArgsParser::new().parse(lexer)?)
}
}

impl std::str::FromStr for IDLType {
type Err = crate::Error;
Expand Down
18 changes: 17 additions & 1 deletion rust/candid/src/parser/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,28 @@ fn load_imports(
}

/// Type check IDLProg and adds bindings to type environment. Returns
/// a hash map for the serivce method signatures. This function ignores the imports.
/// the main actor if present. This function ignores the imports.
pub fn check_prog(te: &mut TypeEnv, prog: &IDLProg) -> Result<Option<Type>> {
let mut env = Env { te, pre: false };
check_decs(&mut env, &prog.decs)?;
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.
pub fn check_init_args(
te: &mut TypeEnv,
main_env: &TypeEnv,
prog: &IDLInitArgs,
) -> Result<Vec<Type>> {
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_type(&env, arg)?);
}
Ok(args)
}

fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option<Type>)> {
let base = if file.is_absolute() {
Expand Down
32 changes: 28 additions & 4 deletions rust/candid/src/types/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,17 +356,41 @@ impl fmt::Display for Field {
/// Construct a field type, which can be used in `TypeInner::Record` and `TypeInner::Variant`.
///
/// `field!{ a: TypeInner::Nat.into() }` expands to `Field { id: Label::Named("a"), ty: ... }`
/// `field!{ 0: TypeInner::Nat.into() }` expands to `Field { id: Label::Id(0), ty: ... }`
/// `field!{ 0: Nat::ty() }` expands to `Field { id: Label::Id(0), ty: ... }`
macro_rules! field {
{ $id:tt : $ty:expr } => {
{ $id:tt : $ty:expr } => {{
$crate::types::internal::Field {
id: match stringify!($id).parse::<u32>() {
Ok(id) => $crate::types::Label::Id(id),
Err(_) => $crate::types::Label::Named(stringify!($id).to_string()),
}.into(),
ty: $ty
}
};
}}
}
#[macro_export]
/// Construct a record type, e.g., `record!{ label: Nat::ty(); 42: String::ty() }`.
macro_rules! record {
{ $($id:tt : $ty:expr);* $(;)? } => {{
let mut fs: Vec<$crate::types::internal::Field> = vec![ $($crate::field!{$id : $ty}),* ];
fs.sort_unstable_by_key(|f| f.id.get_id());
if let Err(e) = $crate::utils::check_unique(fs.iter().map(|f| &f.id)) {
panic!("{e}");
}
Into::<$crate::types::Type>::into($crate::types::TypeInner::Record(fs))
}}
}
#[macro_export]
/// Construct a variant type, e.g., `variant!{ tag: <()>::ty() }`.
macro_rules! variant {
{ $($id:tt : $ty:expr);* $(;)? } => {{
let mut fs: Vec<$crate::types::internal::Field> = vec![ $($crate::field!{$id : $ty}),* ];
fs.sort_unstable_by_key(|f| f.id.get_id());
if let Err(e) = $crate::utils::check_unique(fs.iter().map(|f| &f.id)) {
panic!("{e}");
}
Into::<$crate::types::Type>::into($crate::types::TypeInner::Variant(fs))
}}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -431,7 +455,7 @@ macro_rules! func {
/// `service!{ "f": func!((HttpRequest) -> ()) }` expands to `Type(Rc::new(TypeInner::Service(...)))`
macro_rules! service {
{ $($meth:tt : $ty:expr);* $(;)? } => {{
let mut ms = vec![ $(($meth.to_string(), $ty)),* ];
let mut ms: Vec<(String, $crate::types::Type)> = vec![ $(($meth.to_string(), $ty)),* ];
ms.sort_unstable_by(|a, b| a.0.as_str().partial_cmp(b.0.as_str()).unwrap());
if let Err(e) = $crate::utils::check_unique(ms.iter().map(|m| &m.0)) {
panic!("{e}");
Expand Down
24 changes: 23 additions & 1 deletion rust/candid/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> {
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
/// Take a did file and outputs the init args and the service type (without init args).
/// If the original did file contains imports, the output flatens the type definitions.
/// 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<Type>, (TypeEnv, Type))> {
use crate::types::TypeInner;
Expand All @@ -95,6 +95,28 @@ pub fn instantiate_candid(candid: CandidSource) -> Result<(Vec<Type>, (TypeEnv,
})
}

/// Merge canister metadata candid:args and candid:service into a service constructor.
/// If candid:service already contains init args, returns the original did file.
#[cfg_attr(docsrs, doc(cfg(feature = "parser")))]
#[cfg(feature = "parser")]
pub fn merge_init_args(candid: &str, init: &str) -> Result<(TypeEnv, Type)> {
use crate::parser::{types::IDLInitArgs, typing::check_init_args};
use crate::types::TypeInner;
let candid = CandidSource::Text(candid);
let (env, serv) = candid.load()?;
let serv = serv.ok_or_else(|| Error::msg("the Candid interface has no main service type"))?;
match serv.as_ref() {
TypeInner::Class(_, _) => Ok((env, serv)),
TypeInner::Service(_) => {
let prog = init.parse::<IDLInitArgs>()?;
let mut env2 = TypeEnv::new();
let args = check_init_args(&mut env2, &env, &prog)?;
Ok((env2, TypeInner::Class(args, serv).into()))
}
_ => unreachable!(),
}
}

/// Encode sequence of Rust values into Candid message of type `candid::Result<Vec<u8>>`.
#[macro_export]
macro_rules! Encode {
Expand Down
11 changes: 11 additions & 0 deletions rust/candid/tests/parse_value.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use candid::types::value::{IDLArgs, IDLField, IDLValue, VariantValue};
use candid::types::{Label, Type, TypeEnv, TypeInner};
use candid::{record, variant, CandidType, Nat};

fn parse_args(input: &str) -> IDLArgs {
input.parse().unwrap()
Expand Down Expand Up @@ -136,6 +137,12 @@ fn parse_optional_record() {
let mut args =
parse_args("(opt record {}, record { 1=42;44=\"test\"; 2=false }, variant { 5=null })");
let typ = parse_type("record { 1: nat; 44: text; 2: bool }");
assert_eq!(
typ,
record! { 1: Nat::ty(); 44: String::ty(); 2: bool::ty() }
);
assert_eq!(args.args[0].value_ty(), TypeInner::Opt(record! {}).into());
assert_eq!(args.args[2].value_ty(), variant! { 5: <()>::ty() });
args.args[1] = args.args[1]
.annotate_type(true, &TypeEnv::new(), &typ)
.unwrap();
Expand Down Expand Up @@ -180,6 +187,10 @@ fn parse_nested_record() {
let typ = parse_type(
"record {label: nat; 0x2b:record { test:text; \"opt\":text }; long_label: opt null }",
);
assert_eq!(
typ,
record! {label: Nat::ty(); 43: record!{ test: String::ty(); opt: String::ty() }; long_label: Option::<()>::ty(); }
);
args.args[0] = args.args[0]
.annotate_type(true, &TypeEnv::new(), &typ)
.unwrap();
Expand Down
60 changes: 33 additions & 27 deletions tools/ui/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion tools/ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ members = [

[profile.release]
lto = true
opt-level = 'z'
opt-level = 2

[patch.crates-io.candid]
path = "../../rust/candid"

1 change: 1 addition & 0 deletions tools/ui/src/didjs/didjs.did
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ service : {
did_to_js : (text) -> (opt text) query;
binding : (text, text) -> (opt text) query;
subtype : (new: text, old: text) -> (variant { Ok: null; Err: text }) query;
merge_init_args : (text, text) -> (opt text) query
}
Loading

0 comments on commit a1eb218

Please sign in to comment.