Skip to content

Commit 93d428e

Browse files
authored
Merge pull request #426 from demergent-labs/203-generator-recursion
203 generator recursion
2 parents cd76c8e + fad6c57 commit 93d428e

File tree

8 files changed

+99
-26
lines changed

8 files changed

+99
-26
lines changed

examples/generators/src/generators.did

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,5 @@ type CanisterStatus = variant { "running": null; "stopping": null; "stopped": nu
8585
service: {
8686
"get_randomness_directly": () -> (vec nat8);
8787
"get_randomness_indirectly": () -> (vec nat8);
88+
"get_randomness_super_indirectly": () -> (vec nat8);
8889
}

examples/generators/src/generators.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {
2-
UpdateAsync,
3-
nat8,
4-
ic,
2+
Async,
53
CanisterResult,
6-
Async
4+
nat8,
5+
UpdateAsync
76
} from 'azle';
87
import { ManagementCanister } from 'azle/canisters/management';
98

@@ -18,25 +17,34 @@ export function* get_randomness_directly(): UpdateAsync<nat8[]> {
1817
}
1918
}
2019

21-
// TODO I think the yield's are non-negotiable whenever you call a generator
22-
// TODO To deal with dependencies on the generator result, like if any code depends on the result
23-
// TODO of a generator being called, we need to use a yield
20+
export function* get_randomness_indirectly(): UpdateAsync<nat8[]> {
21+
const indirect_randomness: nat8[] = yield get_randomness();
2422

25-
// TODO I think the appropriate syntax might be to always yield on any generator, and to never use the .next().value syntax
23+
return indirect_randomness;
24+
}
2625

27-
// TODO Yes I think the correct syntax is to always yield the object, never call .next().value, and now we need to recursively
28-
// TODO or emulating recursively walk through the generators
26+
export function* get_randomness_super_indirectly(): UpdateAsync<nat8[]> {
27+
const randomness0: nat8[] = yield get_randomness_level0();
28+
const randomness1: nat8[] = yield get_randomness_level1();
29+
const randomness2: nat8[] = yield get_randomness_level2();
2930

30-
// TODO the problem is that there has to be a direct yield path from the cross canister call
31-
// TODO there can be no indirection, because the informational object that is returned from the
32-
// TODO cross canister call has to be yielding out of the first generator
33-
// TODO you cannot yield another generator, and that I believe is the main issue
34-
// TODO let's hoep this can be solved!!!
31+
return [
32+
...randomness0,
33+
...randomness1,
34+
...randomness2
35+
];
36+
}
3537

36-
export function* get_randomness_indirectly(): UpdateAsync<nat8[]> {
37-
const indirect_randomness: nat8[] = yield get_randomness();
38+
function* get_randomness_level0(): Async<nat8[]> {
39+
return yield get_randomness_level1();
40+
}
3841

39-
return indirect_randomness;
42+
function* get_randomness_level1(): Async<nat8[]> {
43+
return yield get_randomness_level2();
44+
}
45+
46+
function* get_randomness_level2(): Async<nat8[]> {
47+
return yield get_randomness();
4048
}
4149

4250
function* get_randomness(): Async<nat8[]> {

examples/generators/test/dfx_generated/generators/generators.did

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,5 @@ type CanisterStatus = variant { "running": null; "stopping": null; "stopped": nu
8585
service: {
8686
"get_randomness_directly": () -> (vec nat8);
8787
"get_randomness_indirectly": () -> (vec nat8);
88+
"get_randomness_super_indirectly": () -> (vec nat8);
8889
}

examples/generators/test/dfx_generated/generators/generators.did.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,5 @@ export interface UpdateSettingsArgs {
5656
export interface _SERVICE {
5757
'get_randomness_directly' : () => Promise<Array<number>>,
5858
'get_randomness_indirectly' : () => Promise<Array<number>>,
59+
'get_randomness_super_indirectly' : () => Promise<Array<number>>,
5960
}

examples/generators/test/dfx_generated/generators/generators.did.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export const idlFactory = ({ IDL }) => {
22
return IDL.Service({
33
'get_randomness_directly' : IDL.Func([], [IDL.Vec(IDL.Nat8)], []),
44
'get_randomness_indirectly' : IDL.Func([], [IDL.Vec(IDL.Nat8)], []),
5+
'get_randomness_super_indirectly' : IDL.Func([], [IDL.Vec(IDL.Nat8)], []),
56
});
67
};
78
export const init = ({ IDL }) => { return []; };

examples/generators/test/test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,58 @@ const generators_canister = createActor(
1313
}
1414
);
1515

16-
const tests: Test[] = [];
16+
const tests: Test[] = [
17+
{
18+
name: 'clear canister memory',
19+
prep: async () => {
20+
execSync(`dfx canister uninstall-code generators || true`, {
21+
stdio: 'inherit'
22+
});
23+
}
24+
},
25+
{
26+
// TODO hopefully we can get rid of this: https://forum.dfinity.org/t/generated-declarations-in-node-js-environment-break/12686/16?u=lastmjs
27+
name: 'waiting for createActor fetchRootKey',
28+
wait: 5000
29+
},
30+
{
31+
name: 'deploy',
32+
prep: async () => {
33+
execSync(`dfx deploy`, {
34+
stdio: 'inherit'
35+
});
36+
}
37+
},
38+
{
39+
name: 'get_randomness_directly',
40+
test: async () => {
41+
const result = await generators_canister.get_randomness_directly();
42+
43+
return {
44+
ok: result.length === 32
45+
};
46+
}
47+
},
48+
{
49+
name: 'get_randomness_indirectly',
50+
test: async () => {
51+
const result = await generators_canister.get_randomness_indirectly();
52+
53+
return {
54+
ok: result.length === 32
55+
};
56+
}
57+
},
58+
{
59+
name: 'get_randomness_super_indirectly',
60+
test: async () => {
61+
const result = await generators_canister.get_randomness_super_indirectly();
62+
63+
return {
64+
ok: result.length === 96
65+
};
66+
}
67+
}
68+
];
1769

1870
run_tests(tests);

src/compiler/typescript_to_rust/generators/canister_methods/developer_defined/return_value_handler.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,10 @@ export function generateReturnValueHandler(implItemMethod: ImplItemMethod): Rust
101101
// return `serde_json::from_value(_azle_return_value.to_json(&mut boa_context).unwrap()).unwrap()`;
102102
}
103103

104-
// TODO the generator implementation is not sufficient
105-
// TODO I am afraid we might have to do something recursively...well we can just mutate things I guess
106-
// TODO but if we return a generator as the call to next, we need to deal with that as well it seems
104+
// TODO Now that we are using the async_recursion crate we can probably rewrite this entirely recursively (get rid of the mutations)
107105
export function generateHandleGeneratorResultFunction(callFunctionInfos: CallFunctionInfo[]): Rust {
108-
return `
106+
return /* rust */ `
107+
#[async_recursion::async_recursion(?Send)]
109108
async fn handle_generator_result(
110109
_azle_boa_context: &mut boa_engine::Context,
111110
_azle_return_value: &boa_engine::JsValue
@@ -128,12 +127,21 @@ export function generateHandleGeneratorResultFunction(callFunctionInfos: CallFun
128127
let yield_result_done_js_value = yield_result_js_object.get("done", _azle_boa_context).unwrap();
129128
let yield_result_done_bool = yield_result_done_js_value.as_boolean().unwrap();
130129
131-
// TODO we need to handle this return value being a generator
132-
// TODO we will have to emulate recursion with mutations
133130
let yield_result_value_js_value = yield_result_js_object.get("value", _azle_boa_context).unwrap();
134131
135132
if yield_result_done_bool == false {
136133
let yield_result_value_js_object = yield_result_value_js_value.as_object().unwrap();
134+
135+
if yield_result_value_js_object.is_generator() {
136+
let recursed_generator_js_value = handle_generator_result(
137+
_azle_boa_context,
138+
&yield_result_value_js_value
139+
).await;
140+
141+
_azle_args = vec![recursed_generator_js_value];
142+
143+
continue;
144+
}
137145
138146
let name_js_value = yield_result_value_js_object.get("name", _azle_boa_context).unwrap();
139147
let name_string = name_js_value.as_string().unwrap();
@@ -306,7 +314,7 @@ export function generateHandleGeneratorResultFunction(callFunctionInfos: CallFun
306314
307315
match &call_function_name_string[..] {
308316
${callFunctionInfos.map((callFunctionInfo) => {
309-
return `
317+
return /* rust */`
310318
"${callFunctionInfo.functionName}" => {
311319
let canister_id_js_value = call_args_js_object.get("1", _azle_boa_context).unwrap();
312320
let canister_id_principal: ic_cdk::export::Principal = canister_id_js_value.azle_try_from_js_value(_azle_boa_context).unwrap();

src/compiler/typescript_to_rust/generators/cargo_toml_files.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ export function generateLibCargoToml(canisterName: string): Toml {
3737
getrandom = { version = "0.2.3", features = ["custom"] }
3838
serde = "1.0.137"
3939
azle-js-value-derive = { path = "./azle_js_value_derive" }
40+
async-recursion = "1.0.0"
4041
`;
4142
}

0 commit comments

Comments
 (0)