Skip to content

Commit 7779361

Browse files
committed
[Python] Generate async def for F# task { } expressions
1 parent f0d73e8 commit 7779361

File tree

5 files changed

+92
-118
lines changed

5 files changed

+92
-118
lines changed

src/Fable.Cli/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Changed
11+
12+
* [Python] F# `task { }` expressions now generate Python `async def` functions (by @dbrattli)
13+
1014
## 5.0.0-alpha.20 - 2025-12-08
1115

1216
### Added

src/Fable.Compiler/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Changed
11+
12+
* [Python] F# `task { }` expressions now generate Python `async def` functions (by @dbrattli)
13+
1014
## 5.0.0-alpha.19 - 2025-12-08
1115

1216
### Added

src/Fable.Transforms/Python/Fable2Python.Transforms.fs

Lines changed: 42 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ open Util
2222
let iife (com: IPythonCompiler) ctx (expr: Fable.Expr) =
2323
let afe, stmts =
2424
Annotation.transformFunctionWithAnnotations com ctx None [] expr
25-
|||> makeArrowFunctionExpression com ctx None
25+
|||> makeArrowFunctionExpression com ctx None (Some expr.Type)
2626

2727
Expression.call (afe, []), stmts
2828

@@ -98,11 +98,16 @@ let makeArrowFunctionExpression
9898
com
9999
ctx
100100
(name: string option)
101+
(bodyType: Fable.Type option)
101102
(args: Arguments)
102103
(body: Statement list)
103104
returnType
104105
: Expression * Statement list
105106
=
107+
let isAsync =
108+
match bodyType with
109+
| Some typ -> isTaskType typ
110+
| None -> false
106111

107112
let args =
108113
match args.Args with
@@ -153,11 +158,25 @@ let makeArrowFunctionExpression
153158
|> Option.map Identifier
154159
|> Option.defaultWith (fun _ -> Helpers.getUniqueIdentifier "_arrow")
155160

156-
let func = createFunction ident args body [] returnType None
161+
let func =
162+
if isAsync then
163+
createAsyncFunction ident args body [] returnType None
164+
else
165+
createFunction ident args body [] returnType None
166+
157167
Expression.name ident, [ func ]
158168

169+
/// Check if a Fable type is a Task type (should generate async def)
170+
let isTaskType (typ: Fable.Type) =
171+
match typ with
172+
| Fable.DeclaredType(ent, _) -> ent.FullName = Types.taskGeneric
173+
| _ -> false
174+
159175
let createFunction name args body decoratorList returnType (comment: string option) =
160-
createFunctionWithTypeParams name args body decoratorList returnType comment []
176+
createFunctionWithTypeParams name args body decoratorList returnType comment [] false
177+
178+
let createAsyncFunction name args body decoratorList returnType (comment: string option) =
179+
createFunctionWithTypeParams name args body decoratorList returnType comment [] true
161180

162181
let createFunctionWithTypeParams
163182
name
@@ -167,78 +186,19 @@ let createFunctionWithTypeParams
167186
returnType
168187
(comment: string option)
169188
(typeParams: TypeParam list)
189+
(isAsync: bool)
170190
=
171-
let (|Awaitable|_|) expr =
172-
match expr with
173-
| Expression.Call {
174-
Func = Expression.Attribute {
175-
Value = Expression.Name { Id = Identifier "_builder" }
176-
Attr = Identifier "Run"
177-
}
178-
} -> Some expr
179-
| _ -> None
180-
181-
let isAsync =
182-
// function is async is returnType is an Awaitable and the body return a call to _builder.Run
183-
match returnType with
184-
| Subscript { Value = Name { Id = Identifier "Awaitable" } } ->
185-
let rec find body : bool =
186-
body
187-
|> List.tryFind (
188-
function
189-
| Statement.Return {
190-
Value = Some(Expression.IfExp {
191-
Body = Awaitable _
192-
OrElse = Awaitable _
193-
})
194-
} -> true
195-
| Statement.Return { Value = Some(Awaitable _) } -> true
196-
| Statement.If {
197-
Body = body
198-
Else = orElse
199-
} -> find body && find orElse
200-
| _stmt -> false
201-
)
202-
|> Option.isSome
203-
204-
find body
205-
| _ -> false
206-
207-
let rec replace body : Statement list =
208-
body
209-
|> List.map (
210-
function
211-
| Statement.Return {
212-
Value = Some(Expression.IfExp {
213-
Test = test
214-
Body = body
215-
OrElse = orElse
216-
})
217-
} ->
218-
Statement.return' (Expression.ifExp (test, Expression.Await(body), Expression.Await(orElse)))
219-
| Statement.Return { Value = Some(Awaitable(expr)) } -> Statement.return' (Expression.Await expr)
220-
| Statement.If {
221-
Test = test
222-
Body = body
223-
Else = orElse
224-
} -> Statement.if' (test, replace body, orelse = replace orElse)
225-
| stmt -> stmt
226-
)
227-
228-
match isAsync, returnType with
229-
| true, Subscript { Slice = returnType } ->
230-
let body' = replace body
231-
191+
if isAsync then
232192
Statement.asyncFunctionDef (
233193
name = name,
234194
args = args,
235-
body = body',
195+
body = Helpers.wrapReturnWithAwait body,
236196
decoratorList = decoratorList,
237-
returns = returnType,
197+
returns = Helpers.unwrapTaskType returnType,
238198
typeParams = typeParams,
239199
?comment = comment
240200
)
241-
| _ ->
201+
else
242202
Statement.functionDef (
243203
name = name,
244204
args = args,
@@ -563,7 +523,7 @@ let transformObjectExpr
563523
}
564524
| _ -> { args with Args = self :: args.Args }
565525

566-
Statement.functionDef (name, args, body, decorators, returns = returnType)
526+
createFunction name args body decorators returnType None
567527

568528
/// Transform a callable property (delegate) into a method statement
569529
let transformCallableProperty (memb: Fable.ObjectExprMember) (args: Fable.Ident list) (body: Fable.Expr) =
@@ -575,7 +535,7 @@ let transformObjectExpr
575535
let self = Arg.arg "self"
576536
let args = { args with Args = self :: args.Args }
577537

578-
Statement.functionDef (name, args, body, [], returns = returnType)
538+
createFunction name args body [] returnType None
579539

580540
let interfaces, stmts =
581541
match typ with
@@ -1249,7 +1209,7 @@ let transformBindingExprBody (com: IPythonCompiler) (ctx: Context) (var: Fable.I
12491209
let name = Some var.Name
12501210

12511211
Annotation.transformFunctionWithAnnotations com ctx name args body
1252-
|||> makeArrowFunctionExpression com ctx name
1212+
|||> makeArrowFunctionExpression com ctx name (Some body.Type)
12531213
| _ ->
12541214
let expr, stmt = com.TransformAsExpr(ctx, value)
12551215
expr |> wrapIntExpression value.Type, stmt
@@ -1779,11 +1739,11 @@ let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr) : Expressi
17791739

17801740
| Fable.Lambda(arg, body, name) ->
17811741
Annotation.transformFunctionWithAnnotations com ctx name [ arg ] body
1782-
|||> makeArrowFunctionExpression com ctx name
1742+
|||> makeArrowFunctionExpression com ctx name (Some body.Type)
17831743

17841744
| Fable.Delegate(args, body, name, _) ->
17851745
Annotation.transformFunctionWithAnnotations com ctx name args body
1786-
|||> makeArrowFunctionExpression com ctx name
1746+
|||> makeArrowFunctionExpression com ctx name (Some body.Type)
17871747

17881748
| Fable.ObjectExpr([], typ, None) ->
17891749
// Check if the type is an interface
@@ -2011,14 +1971,14 @@ let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy (expr: F
20111971
| Fable.Lambda(arg, body, name) ->
20121972
let expr', stmts =
20131973
transformFunctionWithAnnotations com ctx name [ arg ] body
2014-
|||> makeArrowFunctionExpression com ctx name
1974+
|||> makeArrowFunctionExpression com ctx name (Some body.Type)
20151975

20161976
stmts @ (expr' |> resolveExpr ctx expr.Type returnStrategy)
20171977

20181978
| Fable.Delegate(args, body, name, _) ->
20191979
let expr', stmts =
20201980
transformFunctionWithAnnotations com ctx name args body
2021-
|||> makeArrowFunctionExpression com ctx name
1981+
|||> makeArrowFunctionExpression com ctx name (Some body.Type)
20221982

20231983
stmts @ (expr' |> resolveExpr ctx expr.Type returnStrategy)
20241984

@@ -2832,8 +2792,10 @@ let transformModuleFunction
28322792
let typeParams = calculateTypeParams com ctx info args returnType body.Type
28332793
let name = com.GetIdentifier(ctx, membName |> Naming.toPythonNaming)
28342794

2795+
let isAsync = isTaskType body.Type
2796+
28352797
let stmt =
2836-
createFunctionWithTypeParams name args body' [] returnType info.XmlDoc typeParams
2798+
createFunctionWithTypeParams name args body' [] returnType info.XmlDoc typeParams isAsync
28372799

28382800
let expr = Expression.name name
28392801

@@ -2945,14 +2907,9 @@ let transformAttachedProperty
29452907
let typeParams =
29462908
calculateTypeParams com ctx info arguments returnType memb.Body.Type
29472909

2948-
Statement.functionDef (
2949-
key,
2950-
arguments,
2951-
body = body,
2952-
decoratorList = decorators,
2953-
returns = returnType,
2954-
typeParams = typeParams
2955-
)
2910+
let isAsync = isTaskType memb.Body.Type
2911+
2912+
createFunctionWithTypeParams key arguments body decorators returnType None typeParams isAsync
29562913
|> List.singleton
29572914

29582915
let transformAttachedMethod (com: IPythonCompiler) ctx (info: Fable.MemberFunctionOrValue) (memb: Fable.MemberDecl) =
@@ -2985,15 +2942,9 @@ let transformAttachedMethod (com: IPythonCompiler) ctx (info: Fable.MemberFuncti
29852942
let key = memberFromName com ctx name |> nameFromKey com ctx
29862943

29872944
let typeParams = calculateTypeParams com ctx info args returnType memb.Body.Type
2945+
let isAsync = isTaskType memb.Body.Type
29882946

2989-
Statement.functionDef (
2990-
key,
2991-
args,
2992-
body = body,
2993-
decoratorList = decorators,
2994-
returns = returnType,
2995-
typeParams = typeParams
2996-
)
2947+
createFunctionWithTypeParams key args body decorators returnType None typeParams isAsync
29972948

29982949
let args, body, returnType =
29992950
getMemberArgsAndBody com ctx (Attached isStatic) info.HasSpread memb.Args memb.Body

src/Fable.Transforms/Python/Fable2Python.Util.fs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,21 @@ module Helpers =
891891
let callInfo = Fable.CallInfo.Create(args = [ e ])
892892
makeIdentExpr "str" |> makeCall None Fable.String callInfo
893893

894+
/// Transform return statements to wrap their values with await
895+
let wrapReturnWithAwait (body: Statement list) : Statement list =
896+
body
897+
|> List.map (fun stmt ->
898+
match stmt with
899+
| Statement.Return { Value = Some value } -> Statement.return' (Await value)
900+
| other -> other
901+
)
902+
903+
/// Unwrap Task[T] to T for async function return types
904+
let unwrapTaskType (returnType: Expression) : Expression =
905+
match returnType with
906+
| Subscript { Slice = innerType } -> innerType
907+
| _ -> returnType
908+
894909

895910
/// Expression builders with automatic statement threading
896911
[<RequireQualifiedAccess>]

0 commit comments

Comments
 (0)