Skip to content

Commit b5a337d

Browse files
TseingMangelMaxime
andauthored
feat(dart): add Async runtime and Future interop for dart (#4978)
Co-authored-by: Mangel Maxime <me@mangelmaxime.fr>
1 parent e0536ec commit b5a337d

10 files changed

Lines changed: 1535 additions & 0 deletions

File tree

src/Fable.Core/Fable.Core.Dart.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ type Future<'T> = interface end
2727
[<ImportMember "dart:async">]
2828
type Stream<'T> = interface end
2929

30+
type Async with
31+
static member AwaitFuture(future: Future<'T>) : Async<'T> = nativeOnly
32+
33+
static member StartAsFuture(workflow: Async<'T>, ?token: System.Threading.CancellationToken) : Future<'T> =
34+
nativeOnly
35+
3036
// [<ImportMember "dart:core">]
3137
[<Global>]
3238
let print (item: obj) : unit = nativeOnly

src/Fable.Transforms/Dart/Fable2Dart.fs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,19 @@ module Util =
21582158
bindings @ body, captureExpr
21592159

21602160
| Fable.Sequential exprs ->
2161+
// Fable appends a unit value after a throw, which Dart cannot type
2162+
let exprs =
2163+
match
2164+
exprs
2165+
|> List.tryFindIndex (fun e ->
2166+
match e with
2167+
| Fable.Extended(Fable.Throw _, _) -> true
2168+
| _ -> false
2169+
)
2170+
with
2171+
| Some i -> List.truncate (i + 1) exprs
2172+
| None -> exprs
2173+
21612174
let exprs, lastExpr = List.splitLast exprs
21622175

21632176
let statements1 = exprs |> List.collect (transform com ctx Ignore >> fst)

src/Fable.Transforms/Dart/Replacements.fs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,13 @@ let tryEntityIdent (com: Compiler) entFullName =
632632
match entFullName with
633633
| "Fable.Core.Dart.Future`1" -> makeIdentExpr "Future" |> Some
634634
| "Fable.Core.Dart.Stream`1" -> makeIdentExpr "Stream" |> Some
635+
| "Microsoft.FSharp.Control.FSharpAsync`1" -> makeImportLib com MetaType "Async" "AsyncBuilder" |> Some
636+
| "System.Threading.CancellationToken"
637+
| "System.Threading.CancellationTokenSource" ->
638+
makeImportLib com MetaType "CancellationToken" "AsyncBuilder" |> Some
639+
| "System.Threading.CancellationTokenRegistration" -> makeImportLib com MetaType "IDisposable" "Types" |> Some
640+
| "System.OperationCanceledException" ->
641+
makeImportLib com MetaType "OperationCanceledException" "AsyncBuilder" |> Some
635642
| BuiltinDefinition BclDateOnly
636643
| BuiltinDefinition BclDateTime
637644
| BuiltinDefinition BclDateTimeOffset -> makeIdentExpr "DateTime" |> Some
@@ -715,6 +722,8 @@ let tryCoreOp com r t coreModule coreMember args =
715722
let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
716723
match i.DeclaringEntityFullName, i.CompiledName with
717724
| _, UniversalFableCoreHelpers com ctx r t i args error expr -> Some expr
725+
| _, "Async.AwaitFuture.Static" -> Helper.LibCall(com, "Async", "awaitFuture", t, args, ?loc = r) |> Some
726+
| _, "Async.StartAsFuture.Static" -> Helper.LibCall(com, "Async", "startAsFuture", t, args, ?loc = r) |> Some
718727
| "Fable.Core.Reflection", meth -> Helper.LibCall(com, "Reflection", meth, t, args, ?loc = r) |> Some
719728
| "Fable.Core.Compiler", meth ->
720729
match meth with
@@ -2614,6 +2623,9 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
26142623
match i.DeclaringEntityFullName with
26152624
| "System.Collections.Generic.KeyNotFoundException"
26162625
| BuiltinSystemException _ -> bclType com ctx r t i thisArg args
2626+
| "System.OperationCanceledException" ->
2627+
let e = makeImportLib com Any "OperationCanceledException" "AsyncBuilder"
2628+
Helper.ConstructorCall(e, t, args, ?loc = r) |> Some
26172629
| _ ->
26182630
let e = makeImportLib com Any "ExceptionBase" "Types"
26192631
Helper.ConstructorCall(e, t, args, ?loc = r) |> Some
@@ -4030,6 +4042,7 @@ let private replacedModules =
40304042
"System.Random", random
40314043
"System.Threading.CancellationToken", cancels
40324044
"System.Threading.CancellationTokenSource", cancels
4045+
"System.Threading.CancellationTokenRegistration", disposables
40334046
"System.Threading.Monitor", monitor
40344047
"System.Activator", activator
40354048
"System.Text.Encoding", encoding

src/fable-library-dart/Async.dart

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
import 'dart:async' as dart_async;
2+
3+
import 'AsyncBuilder.dart' as async_builder;
4+
import 'Choice.dart' as choice;
5+
import 'System.dart' as system;
6+
import 'Types.dart' as types;
7+
8+
void _emptyContinuation<T>(T _value) {}
9+
10+
async_builder.Async<T> makeAsync<T>(async_builder.Async<T> body) {
11+
return body;
12+
}
13+
14+
void invoke<T>(
15+
async_builder.Async<T> computation,
16+
async_builder.IAsyncContext<T> ctx,
17+
) {
18+
computation(ctx);
19+
}
20+
21+
void callThenInvoke<T, U>(
22+
async_builder.IAsyncContext<T> ctx,
23+
U result1,
24+
Function part2,
25+
) {
26+
async_builder.invokeBinderAsAsync<U, T>(part2, result1)(ctx);
27+
}
28+
29+
void bind<T, U>(
30+
async_builder.IAsyncContext<T> ctx,
31+
async_builder.Async<U> part1,
32+
Function part2,
33+
) {
34+
async_builder.protectedBind<U, T>(part1, part2)(ctx);
35+
}
36+
37+
Duration _cancellationDelay(Object? value) {
38+
if (value is Duration) {
39+
return value;
40+
}
41+
42+
if (value is num) {
43+
return Duration(milliseconds: value.toInt());
44+
}
45+
46+
throw ArgumentError.value(
47+
value,
48+
'value',
49+
'Expected Duration or milliseconds.',
50+
);
51+
}
52+
53+
async_builder.CancellationToken createCancellationToken([Object? arg]) {
54+
final token = async_builder.CancellationToken(arg is bool ? arg : false);
55+
56+
if (arg is Duration || arg is num) {
57+
token.cancelAfter(_cancellationDelay(arg));
58+
}
59+
60+
return token;
61+
}
62+
63+
void cancel(async_builder.CancellationToken token) {
64+
token.cancel();
65+
}
66+
67+
void cancelAfter(async_builder.CancellationToken token, Object delay) {
68+
token.cancelAfter(_cancellationDelay(delay));
69+
}
70+
71+
bool isCancellationRequested(async_builder.CancellationToken? token) {
72+
return token?.isCancelled ?? false;
73+
}
74+
75+
void throwIfCancellationRequested(async_builder.CancellationToken? token) {
76+
if (token != null && token.isCancelled) {
77+
throw async_builder.OperationCanceledException();
78+
}
79+
}
80+
81+
async_builder.Async<async_builder.CancellationToken> cancellationToken() {
82+
return async_builder.protectedCont((ctx) => ctx.onSuccess(ctx.cancelToken));
83+
}
84+
85+
final defaultCancellationToken = async_builder.CancellationToken();
86+
87+
async_builder.Async<choice.FSharpChoice$2<T, dynamic>> catchAsync<T>(
88+
async_builder.Async<T> work,
89+
) {
90+
return async_builder.protectedCont<choice.FSharpChoice$2<T, dynamic>>((ctx) {
91+
work(
92+
async_builder.IAsyncContext<T>(
93+
onSuccess: (value) {
94+
ctx.onSuccess(choice.Choice_makeChoice1Of2<T, dynamic>(value));
95+
},
96+
onError: (error) {
97+
ctx.onSuccess(choice.Choice_makeChoice2Of2<dynamic, T>(error));
98+
},
99+
onCancel: ctx.onCancel,
100+
cancelToken: ctx.cancelToken,
101+
trampoline: ctx.trampoline,
102+
),
103+
);
104+
});
105+
}
106+
107+
async_builder.Async<T> fromContinuations<T>(
108+
void Function(async_builder.Continuations<T>) function,
109+
) {
110+
return async_builder.protectedCont<T>((ctx) {
111+
function(
112+
types.Tuple3<
113+
async_builder.Continuation<T>,
114+
async_builder.Continuation<dynamic>,
115+
async_builder.Continuation<async_builder.OperationCanceledException>
116+
>(ctx.onSuccess, ctx.onError, ctx.onCancel),
117+
);
118+
});
119+
}
120+
121+
async_builder.Async<async_builder.Async<T>> startChild<T>(
122+
async_builder.Async<T> computation, [
123+
types.Some<int>? millisecondsTimeout,
124+
]) {
125+
return async_builder.protectedCont<async_builder.Async<T>>((ctx) {
126+
final future = startAsFuture<T>(computation, types.Some(ctx.cancelToken));
127+
128+
var futureToRun = future;
129+
130+
final timeout = millisecondsTimeout?.value;
131+
132+
if (timeout != null && timeout > 0) {
133+
futureToRun = future.timeout(
134+
Duration(milliseconds: timeout),
135+
onTimeout: () => throw system.TimeoutException_$ctor(),
136+
);
137+
}
138+
139+
// In F# an unobserved child's failure is not propagated.
140+
futureToRun.ignore();
141+
142+
async_builder.protectedReturn(awaitFuture<T>(futureToRun))(ctx);
143+
});
144+
}
145+
146+
async_builder.Async<List<T>> parallel<T>(
147+
Iterable<async_builder.Async<T>> computations,
148+
) {
149+
return async_builder.protectedCont<List<T>>((ctx) {
150+
final futures = computations
151+
.map((w) => startAsFuture<T>(w, types.Some(ctx.cancelToken)))
152+
.toList(growable: false);
153+
154+
awaitFuture<List<T>>(dart_async.Future.wait<T>(futures, eagerError: true))(
155+
ctx,
156+
);
157+
});
158+
}
159+
160+
async_builder.Async<List<T>> sequential<T>(
161+
Iterable<async_builder.Async<T>> computations,
162+
) {
163+
dart_async.Future<List<T>> run(
164+
async_builder.CancellationToken cancelToken,
165+
) async {
166+
final results = <T>[];
167+
168+
for (final computation in computations) {
169+
results.add(await startAsFuture<T>(computation, types.Some(cancelToken)));
170+
}
171+
172+
return results;
173+
}
174+
175+
return async_builder.protectedCont<List<T>>((ctx) {
176+
awaitFuture<List<T>>(run(ctx.cancelToken))(ctx);
177+
});
178+
}
179+
180+
async_builder.Async<void> ignore<T>(async_builder.Async<T> computation) {
181+
return async_builder.protectedBind<T, void>(
182+
computation,
183+
(_) => async_builder.protectedReturn<void>(null),
184+
);
185+
}
186+
187+
void start<T>(
188+
async_builder.Async<T> computation, [
189+
types.Some<async_builder.CancellationToken>? cancellationToken,
190+
]) {
191+
startWithContinuations<T>(
192+
computation,
193+
_emptyContinuation<T>,
194+
(dynamic error) {
195+
throw error;
196+
},
197+
_emptyContinuation<async_builder.OperationCanceledException>,
198+
cancellationToken,
199+
);
200+
}
201+
202+
void startImmediate<T>(
203+
async_builder.Async<T> computation, [
204+
types.Some<async_builder.CancellationToken>? cancellationToken,
205+
]) {
206+
start<T>(computation, cancellationToken);
207+
}
208+
209+
void startWithContinuations<T>(
210+
async_builder.Async<T> computation,
211+
Function continuation,
212+
async_builder.Continuation<dynamic> exceptionContinuation,
213+
async_builder.Continuation<async_builder.OperationCanceledException>
214+
cancellationContinuation, [
215+
types.Some<async_builder.CancellationToken>? cancelToken,
216+
]) {
217+
final trampoline = async_builder.Trampoline();
218+
219+
async_builder.Continuation<T> doneSuccess(Function cont) {
220+
return (value) {
221+
trampoline.completed = true;
222+
223+
if (cont is Function()) {
224+
cont();
225+
} else {
226+
cont(value);
227+
}
228+
};
229+
}
230+
231+
async_builder.Continuation<U> done<U>(async_builder.Continuation<U> cont) {
232+
return (value) {
233+
trampoline.completed = true;
234+
cont(value);
235+
};
236+
}
237+
238+
computation(
239+
async_builder.IAsyncContext<T>(
240+
onSuccess: doneSuccess(continuation),
241+
onError: done<dynamic>(exceptionContinuation),
242+
onCancel: done<async_builder.OperationCanceledException>(
243+
cancellationContinuation,
244+
),
245+
cancelToken: cancelToken?.value ?? defaultCancellationToken,
246+
trampoline: trampoline,
247+
),
248+
);
249+
}
250+
251+
async_builder.Async<void> sleep(Object delay) {
252+
return async_builder.protectedCont<void>((ctx) {
253+
late final dart_async.Timer timer;
254+
int? listenerId;
255+
var completed = false;
256+
257+
timer = dart_async.Timer(_cancellationDelay(delay), () {
258+
if (completed) {
259+
return;
260+
}
261+
262+
completed = true;
263+
264+
final id = listenerId;
265+
266+
if (id != null) {
267+
ctx.cancelToken.removeListener(id);
268+
}
269+
270+
ctx.onSuccess(null);
271+
});
272+
273+
listenerId = ctx.cancelToken.addListener(() {
274+
if (completed) {
275+
return;
276+
}
277+
278+
completed = true;
279+
timer.cancel();
280+
ctx.onCancel(async_builder.OperationCanceledException());
281+
});
282+
});
283+
}
284+
285+
async_builder.Async<T> awaitFuture<T>(dart_async.Future<T> future) {
286+
return async_builder.protectedCont<T>((ctx) {
287+
future.then<void>(
288+
(value) {
289+
ctx.onSuccess(value);
290+
},
291+
onError: (Object error, StackTrace stackTrace) {
292+
if (error is async_builder.OperationCanceledException) {
293+
ctx.onCancel(error);
294+
} else {
295+
ctx.onError(error);
296+
}
297+
},
298+
);
299+
});
300+
}
301+
302+
dart_async.Future<T> startAsFuture<T>(
303+
async_builder.Async<T> computation, [
304+
types.Some<async_builder.CancellationToken>? cancellationToken,
305+
]) {
306+
final completer = dart_async.Completer<T>();
307+
308+
startWithContinuations<T>(
309+
computation,
310+
(value) {
311+
completer.complete(value);
312+
},
313+
(dynamic error) {
314+
completer.completeError(error);
315+
},
316+
(error) {
317+
completer.completeError(error);
318+
},
319+
cancellationToken,
320+
);
321+
322+
return completer.future;
323+
}

0 commit comments

Comments
 (0)