Skip to content

Commit

Permalink
import service (#507)
Browse files Browse the repository at this point in the history
* import service

* test

* fix

* fix

* fix
  • Loading branch information
chenyan-dfinity committed Dec 15, 2023
1 parent 491969f commit 0fbcb24
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 43 deletions.
3 changes: 2 additions & 1 deletion rust/candid_parser/src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ MethTyp: Binding = {
// Type declarations
Def: Dec = {
"type" <id:"id"> "=" <t:Typ> => Dec::TypD(Binding { id: id, typ: t }),
"import" <Text> => Dec::ImportD(<>),
"import" <Text> => Dec::ImportType(<>),
"import" "service" <Text> => Dec::ImportServ(<>),
}

Actor: IDLType = {
Expand Down
3 changes: 2 additions & 1 deletion rust/candid_parser/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ pub struct TypeField {
#[derive(Debug)]
pub enum Dec {
TypD(Binding),
ImportD(String),
ImportType(String),
ImportServ(String),
}

#[derive(Debug, Clone)]
Expand Down
100 changes: 79 additions & 21 deletions rust/candid_parser/src/typing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::types::*;
use crate::{pretty_parse, Error, Result};
use candid::types::{Field, Function, Type, TypeEnv, TypeInner};
use std::collections::BTreeSet;
use candid::utils::check_unique;
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};

pub struct Env<'a> {
Expand Down Expand Up @@ -135,7 +136,7 @@ fn check_defs(env: &mut Env, decs: &[Dec]) -> Result<()> {
let t = check_type(env, typ)?;
env.te.0.insert(id.to_string(), t);
}
Dec::ImportD(_) => (),
Dec::ImportType(_) | Dec::ImportServ(_) => (),
}
}
Ok(())
Expand Down Expand Up @@ -214,24 +215,29 @@ fn resolve_path(base: &Path, file: &str) -> PathBuf {
fn load_imports(
is_pretty: bool,
base: &Path,
visited: &mut BTreeSet<PathBuf>,
visited: &mut BTreeMap<PathBuf, bool>,
prog: &IDLProg,
list: &mut Vec<PathBuf>,
list: &mut Vec<(PathBuf, String)>,
) -> Result<()> {
for dec in prog.decs.iter() {
if let Dec::ImportD(file) = dec {
let include_serv = matches!(dec, Dec::ImportServ(_));
if let Dec::ImportType(file) | Dec::ImportServ(file) = dec {
let path = resolve_path(base, file);
if visited.insert(path.clone()) {
let code = std::fs::read_to_string(&path)
.map_err(|_| Error::msg(format!("Cannot import {file:?}")))?;
let code = if is_pretty {
pretty_parse::<IDLProg>(path.to_str().unwrap(), &code)?
} else {
code.parse::<IDLProg>()?
};
let base = path.parent().unwrap();
load_imports(is_pretty, base, visited, &code, list)?;
list.push(path);
match visited.get_mut(&path) {
Some(x) => *x = *x || include_serv,
None => {
visited.insert(path.clone(), include_serv);
let code = std::fs::read_to_string(&path)
.map_err(|_| Error::msg(format!("Cannot import {file:?}")))?;
let code = if is_pretty {
pretty_parse::<IDLProg>(path.to_str().unwrap(), &code)?
} else {
code.parse::<IDLProg>()?
};
let base = path.parent().unwrap();
load_imports(is_pretty, base, visited, &code, list)?;
list.push((path, file.to_string()));
}
}
}
}
Expand Down Expand Up @@ -262,6 +268,42 @@ pub fn check_init_args(
Ok(args)
}

fn merge_actor(
env: &Env,
actor: &Option<Type>,
imported: &Option<Type>,
file: &str,
) -> Result<Option<Type>> {
match imported {
None => Err(Error::msg(format!(
"Imported service file {file:?} has no main service"
))),
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"
))),
TypeInner::Service(meths) => match actor {
None => Ok(Some(t)),
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.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))
}
},
_ => unreachable!(),
}
}
}
}

fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option<Type>)> {
let base = if file.is_absolute() {
file.parent().unwrap().to_path_buf()
Expand All @@ -279,21 +321,37 @@ fn check_file_(file: &Path, is_pretty: bool) -> Result<(TypeEnv, Option<Type>)>
} else {
prog.parse::<IDLProg>()?
};
let mut visited = BTreeMap::new();
let mut imports = Vec::new();
load_imports(is_pretty, &base, &mut BTreeSet::new(), &prog, &mut imports)?;
load_imports(is_pretty, &base, &mut visited, &prog, &mut imports)?;
let imports: Vec<_> = imports
.iter()
.map(|file| match visited.get(&file.0) {
Some(x) => (*x, &file.0, &file.1),
None => unreachable!(),
})
.collect();
let mut te = TypeEnv::new();
let mut env = Env {
te: &mut te,
pre: false,
};
for import in imports.iter() {
let code = std::fs::read_to_string(import)?;
let mut actor: Option<Type> = None;
for (include_serv, path, name) in imports.iter() {
let code = std::fs::read_to_string(path)?;
let code = code.parse::<IDLProg>()?;
check_decs(&mut env, &code.decs)?;
if *include_serv {
let t = check_actor(&env, &code.actor)?;
actor = merge_actor(&env, &actor, &t, name)?;
}
}
check_decs(&mut env, &prog.decs)?;
let actor = check_actor(&env, &prog.actor)?;
Ok((te, actor))
let mut res = check_actor(&env, &prog.actor)?;
if actor.is_some() {
res = merge_actor(&env, &res, &actor, "")?;
}
Ok((te, res))
}

/// Type check did file including the imports.
Expand Down
1 change: 1 addition & 0 deletions rust/candid_parser/tests/assets/bad_import2.did
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import service "import/a.did";
2 changes: 2 additions & 0 deletions rust/candid_parser/tests/assets/bad_import3.did
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import service "actor.did";
import service "example.did";
8 changes: 4 additions & 4 deletions rust/candid_parser/tests/assets/example.did
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "recursion.did";
import service "recursion.did";
import "import/a.did";
import "import/b/b.did";
import service "import/b/b.did";
type my_type = principal;
type List = opt record { head: int; tail: List };
type f = func (List, func (int32) -> (int64)) -> (opt List);
Expand All @@ -11,8 +11,8 @@ type broker = service {
type nested = record { nat; nat; record {nat;int;}; record { nat; 0x2a:nat; nat8; }; 42:nat; 40:nat; variant{ A; 0x2a; B; C }; };

service server : {
f : (list, test: blob, opt bool) -> () oneway;
g : (my_type, List, opt List, nested) -> (int, broker) query;
f1 : (list, test: blob, opt bool) -> () oneway;
g1 : (my_type, List, opt List, nested) -> (int, broker) query;
h : (vec opt text, variant { A: nat; B: opt text }, opt List) -> (record { id: nat; 0x2a: record {} });
i : f;
x : (a,b) -> (opt a, opt b) composite_query;
Expand Down
3 changes: 3 additions & 0 deletions rust/candid_parser/tests/assets/import/a.did
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
import "b/b.did";
type a = variant {a;b:b};
service : (a,b) -> {
f : (b) -> (a);
}
3 changes: 3 additions & 0 deletions rust/candid_parser/tests/assets/import/b/b.did
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
type b = record { int;nat };
service : {
bbbbb : (b) -> ();
}
1 change: 1 addition & 0 deletions rust/candid_parser/tests/assets/ok/bad_import2.fail
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Imported service file "import/a.did" has a service constructor
1 change: 1 addition & 0 deletions rust/candid_parser/tests/assets/ok/bad_import3.fail
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Duplicate imported method name: label 'f' hash collision with 'f'
7 changes: 5 additions & 2 deletions rust/candid_parser/tests/assets/ok/example.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ export type tree = {
} |
{ 'leaf' : bigint };
export interface _SERVICE {
'f' : ActorMethod<[list, Uint8Array | number[], [] | [boolean]], undefined>,
'g' : ActorMethod<[my_type, List, [] | [List], nested], [bigint, Principal]>,
'bbbbb' : ActorMethod<[b], undefined>,
'f' : t,
'f1' : ActorMethod<[list, Uint8Array | number[], [] | [boolean]], undefined>,
'g' : ActorMethod<[list], [B, tree, stream]>,
'g1' : ActorMethod<[my_type, List, [] | [List], nested], [bigint, Principal]>,
'h' : ActorMethod<
[
Array<[] | [string]>,
Expand Down
7 changes: 5 additions & 2 deletions rust/candid_parser/tests/assets/ok/example.did
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ type tree = variant {
leaf : int;
};
service : {
f : (list, vec nat8, opt bool) -> () oneway;
g : (my_type, List, opt List, nested) -> (int, broker) query;
bbbbb : (b) -> ();
f : t;
f1 : (list, vec nat8, opt bool) -> () oneway;
g : (list) -> (B, tree, stream);
g1 : (my_type, List, opt List, nested) -> (int, broker) query;
h : (vec opt text, variant { A : nat; B : opt text }, opt List) -> (
record { 42 : record {}; id : nat },
);
Expand Down
34 changes: 31 additions & 3 deletions rust/candid_parser/tests/assets/ok/example.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
export const idlFactory = ({ IDL }) => {
const B = IDL.Rec();
const List = IDL.Rec();
const list = IDL.Rec();
const stream = IDL.Rec();
const t = IDL.Rec();
const tree = IDL.Rec();
const b = IDL.Tuple(IDL.Int, IDL.Nat);
const node = IDL.Record({ 'head' : IDL.Nat, 'tail' : list });
list.fill(IDL.Opt(node));
const A = B;
B.fill(IDL.Opt(A));
tree.fill(
IDL.Variant({
'branch' : IDL.Record({ 'val' : IDL.Int, 'left' : tree, 'right' : tree }),
'leaf' : IDL.Int,
})
);
stream.fill(
IDL.Opt(
IDL.Record({
'head' : IDL.Nat,
'next' : IDL.Func([], [stream], ['query']),
})
)
);
const s = IDL.Service({
'f' : t,
'g' : IDL.Func([list], [B, tree, stream], []),
});
t.fill(IDL.Func([s], [], []));
const my_type = IDL.Principal;
List.fill(IDL.Opt(IDL.Record({ 'head' : IDL.Int, 'tail' : List })));
const nested = IDL.Record({
Expand Down Expand Up @@ -36,15 +62,17 @@ export const idlFactory = ({ IDL }) => {
[IDL.Opt(List)],
[],
);
const b = IDL.Tuple(IDL.Int, IDL.Nat);
const a = IDL.Variant({ 'a' : IDL.Null, 'b' : b });
return IDL.Service({
'f' : IDL.Func(
'bbbbb' : IDL.Func([b], [], []),
'f' : t,
'f1' : IDL.Func(
[list, IDL.Vec(IDL.Nat8), IDL.Opt(IDL.Bool)],
[],
['oneway'],
),
'g' : IDL.Func(
'g' : IDL.Func([list], [B, tree, stream], []),
'g1' : IDL.Func(
[my_type, List, IDL.Opt(List), nested],
[IDL.Int, broker],
['query'],
Expand Down
7 changes: 5 additions & 2 deletions rust/candid_parser/tests/assets/ok/example.mo
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ module {
#leaf : Int;
};
public type Self = actor {
f : shared (list, Blob, ?Bool) -> ();
g : shared query (my_type, List, ?List, nested) -> async (Int, broker);
bbbbb : shared b -> async ();
f : t;
f1 : shared (list, Blob, ?Bool) -> ();
g : shared list -> async (B, tree, stream);
g1 : shared query (my_type, List, ?List, nested) -> async (Int, broker);
h : shared ([?Text], { #A : Nat; #B : ?Text }, ?List) -> async {
_42_ : {};
id : Nat;
Expand Down
47 changes: 40 additions & 7 deletions rust/candid_parser/tests/assets/ok/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,39 @@
use candid::{self, CandidType, Deserialize, Principal, Encode, Decode};
use ic_cdk::api::call::CallResult as Result;

#[derive(CandidType, Deserialize)]
pub struct B (pub candid::Int,pub candid::Nat,);

#[derive(CandidType, Deserialize)]
pub struct Node { pub head: candid::Nat, pub tail: Box<List> }

#[derive(CandidType, Deserialize)]
pub struct List(Option<Node>);

pub type A = Box<B>;
#[derive(CandidType, Deserialize)]
pub struct B(Option<A>);

#[derive(CandidType, Deserialize)]
pub enum Tree {
#[serde(rename="branch")]
Branch{ val: candid::Int, left: Box<Tree>, right: Box<Tree> },
#[serde(rename="leaf")]
Leaf(candid::Int),
}

candid::define_function!(pub StreamInnerNext : () -> (Stream) query);
#[derive(CandidType, Deserialize)]
pub struct StreamInner { pub head: candid::Nat, pub next: StreamInnerNext }

#[derive(CandidType, Deserialize)]
pub struct Stream(Option<StreamInner>);

candid::define_service!(pub S : {
"f" : T::ty();
"g" : candid::func!((List) -> (B, Tree, Stream));
});
candid::define_function!(pub T : (S) -> ());
pub type MyType = Principal;
#[derive(CandidType, Deserialize)]
pub struct ListInner { pub head: candid::Int, pub tail: Box<List> }
Expand Down Expand Up @@ -52,28 +79,34 @@ pub struct HRet { pub _42_: HRet42, pub id: candid::Nat }

candid::define_function!(pub FArg1 : (i32) -> (i64));
candid::define_function!(pub F : (List, FArg1) -> (Option<List>));
#[derive(CandidType, Deserialize)]
pub struct B (pub candid::Int,pub candid::Nat,);

#[derive(CandidType, Deserialize)]
pub enum A { #[serde(rename="a")] A, #[serde(rename="b")] B(B) }

pub struct Service(pub Principal);
impl Service {
pub async fn f(
pub async fn bbbbb(&self, arg0: B) -> Result<()> {
ic_cdk::call(self.0, "bbbbb", (arg0,)).await
}
pub async fn f(&self, arg0: S) -> Result<()> {
ic_cdk::call(self.0, "f", (arg0,)).await
}
pub async fn f_1(
&self,
arg0: List,
arg1: serde_bytes::ByteBuf,
arg2: Option<bool>,
) -> Result<()> { ic_cdk::call(self.0, "f", (arg0,arg1,arg2,)).await }
pub async fn g(
) -> Result<()> { ic_cdk::call(self.0, "f1", (arg0,arg1,arg2,)).await }
pub async fn g(&self, arg0: List) -> Result<(B,Tree,Stream,)> {
ic_cdk::call(self.0, "g", (arg0,)).await
}
pub async fn g_1(
&self,
arg0: MyType,
arg1: List,
arg2: Option<List>,
arg3: Nested,
) -> Result<(candid::Int,Broker,)> {
ic_cdk::call(self.0, "g", (arg0,arg1,arg2,arg3,)).await
ic_cdk::call(self.0, "g1", (arg0,arg1,arg2,arg3,)).await
}
pub async fn h(
&self,
Expand Down

0 comments on commit 0fbcb24

Please sign in to comment.