Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Changed

* [Python] F# `task { }` expressions now generate Python `async def` functions (by @dbrattli)

## 5.0.0-alpha.20 - 2025-12-08

### Added
Expand Down
4 changes: 4 additions & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Changed

* [Python] F# `task { }` expressions now generate Python `async def` functions (by @dbrattli)

## 5.0.0-alpha.19 - 2025-12-08

### Added
Expand Down
133 changes: 42 additions & 91 deletions src/Fable.Transforms/Python/Fable2Python.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ open Util
let iife (com: IPythonCompiler) ctx (expr: Fable.Expr) =
let afe, stmts =
Annotation.transformFunctionWithAnnotations com ctx None [] expr
|||> makeArrowFunctionExpression com ctx None
|||> makeArrowFunctionExpression com ctx None (Some expr.Type)

Expression.call (afe, []), stmts

Expand Down Expand Up @@ -98,11 +98,16 @@ let makeArrowFunctionExpression
com
ctx
(name: string option)
(bodyType: Fable.Type option)
(args: Arguments)
(body: Statement list)
returnType
: Expression * Statement list
=
let isAsync =
match bodyType with
| Some typ -> isTaskType typ
| None -> false

let args =
match args.Args with
Expand Down Expand Up @@ -153,11 +158,25 @@ let makeArrowFunctionExpression
|> Option.map Identifier
|> Option.defaultWith (fun _ -> Helpers.getUniqueIdentifier "_arrow")

let func = createFunction ident args body [] returnType None
let func =
if isAsync then
createAsyncFunction ident args body [] returnType None
else
createFunction ident args body [] returnType None

Expression.name ident, [ func ]

/// Check if a Fable type is a Task type (should generate async def)
let isTaskType (typ: Fable.Type) =
match typ with
| Fable.DeclaredType(ent, _) -> ent.FullName = Types.taskGeneric
| _ -> false

let createFunction name args body decoratorList returnType (comment: string option) =
createFunctionWithTypeParams name args body decoratorList returnType comment []
createFunctionWithTypeParams name args body decoratorList returnType comment [] false

let createAsyncFunction name args body decoratorList returnType (comment: string option) =
createFunctionWithTypeParams name args body decoratorList returnType comment [] true

let createFunctionWithTypeParams
name
Expand All @@ -167,78 +186,19 @@ let createFunctionWithTypeParams
returnType
(comment: string option)
(typeParams: TypeParam list)
(isAsync: bool)
=
let (|Awaitable|_|) expr =
match expr with
| Expression.Call {
Func = Expression.Attribute {
Value = Expression.Name { Id = Identifier "_builder" }
Attr = Identifier "Run"
}
} -> Some expr
| _ -> None

let isAsync =
// function is async is returnType is an Awaitable and the body return a call to _builder.Run
match returnType with
| Subscript { Value = Name { Id = Identifier "Awaitable" } } ->
let rec find body : bool =
body
|> List.tryFind (
function
| Statement.Return {
Value = Some(Expression.IfExp {
Body = Awaitable _
OrElse = Awaitable _
})
} -> true
| Statement.Return { Value = Some(Awaitable _) } -> true
| Statement.If {
Body = body
Else = orElse
} -> find body && find orElse
| _stmt -> false
)
|> Option.isSome

find body
| _ -> false

let rec replace body : Statement list =
body
|> List.map (
function
| Statement.Return {
Value = Some(Expression.IfExp {
Test = test
Body = body
OrElse = orElse
})
} ->
Statement.return' (Expression.ifExp (test, Expression.Await(body), Expression.Await(orElse)))
| Statement.Return { Value = Some(Awaitable(expr)) } -> Statement.return' (Expression.Await expr)
| Statement.If {
Test = test
Body = body
Else = orElse
} -> Statement.if' (test, replace body, orelse = replace orElse)
| stmt -> stmt
)

match isAsync, returnType with
| true, Subscript { Slice = returnType } ->
let body' = replace body

if isAsync then
Statement.asyncFunctionDef (
name = name,
args = args,
body = body',
body = Helpers.wrapReturnWithAwait body,
decoratorList = decoratorList,
returns = returnType,
returns = Helpers.unwrapTaskType returnType,
typeParams = typeParams,
?comment = comment
)
| _ ->
else
Statement.functionDef (
name = name,
args = args,
Expand Down Expand Up @@ -563,7 +523,7 @@ let transformObjectExpr
}
| _ -> { args with Args = self :: args.Args }

Statement.functionDef (name, args, body, decorators, returns = returnType)
createFunction name args body decorators returnType None

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

Statement.functionDef (name, args, body, [], returns = returnType)
createFunction name args body [] returnType None

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

Annotation.transformFunctionWithAnnotations com ctx name args body
|||> makeArrowFunctionExpression com ctx name
|||> makeArrowFunctionExpression com ctx name (Some body.Type)
| _ ->
let expr, stmt = com.TransformAsExpr(ctx, value)
expr |> wrapIntExpression value.Type, stmt
Expand Down Expand Up @@ -1779,11 +1739,11 @@ let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr) : Expressi

| Fable.Lambda(arg, body, name) ->
Annotation.transformFunctionWithAnnotations com ctx name [ arg ] body
|||> makeArrowFunctionExpression com ctx name
|||> makeArrowFunctionExpression com ctx name (Some body.Type)

| Fable.Delegate(args, body, name, _) ->
Annotation.transformFunctionWithAnnotations com ctx name args body
|||> makeArrowFunctionExpression com ctx name
|||> makeArrowFunctionExpression com ctx name (Some body.Type)

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

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

| Fable.Delegate(args, body, name, _) ->
let expr', stmts =
transformFunctionWithAnnotations com ctx name args body
|||> makeArrowFunctionExpression com ctx name
|||> makeArrowFunctionExpression com ctx name (Some body.Type)

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

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

let isAsync = isTaskType body.Type

let stmt =
createFunctionWithTypeParams name args body' [] returnType info.XmlDoc typeParams
createFunctionWithTypeParams name args body' [] returnType info.XmlDoc typeParams isAsync

let expr = Expression.name name

Expand Down Expand Up @@ -2945,14 +2907,9 @@ let transformAttachedProperty
let typeParams =
calculateTypeParams com ctx info arguments returnType memb.Body.Type

Statement.functionDef (
key,
arguments,
body = body,
decoratorList = decorators,
returns = returnType,
typeParams = typeParams
)
let isAsync = isTaskType memb.Body.Type

createFunctionWithTypeParams key arguments body decorators returnType None typeParams isAsync
|> List.singleton

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

let typeParams = calculateTypeParams com ctx info args returnType memb.Body.Type
let isAsync = isTaskType memb.Body.Type

Statement.functionDef (
key,
args,
body = body,
decoratorList = decorators,
returns = returnType,
typeParams = typeParams
)
createFunctionWithTypeParams key args body decorators returnType None typeParams isAsync

let args, body, returnType =
getMemberArgsAndBody com ctx (Attached isStatic) info.HasSpread memb.Args memb.Body
Expand Down
15 changes: 15 additions & 0 deletions src/Fable.Transforms/Python/Fable2Python.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,21 @@ module Helpers =
let callInfo = Fable.CallInfo.Create(args = [ e ])
makeIdentExpr "str" |> makeCall None Fable.String callInfo

/// Transform return statements to wrap their values with await
let wrapReturnWithAwait (body: Statement list) : Statement list =
body
|> List.map (fun stmt ->
match stmt with
| Statement.Return { Value = Some value } -> Statement.return' (Await value)
| other -> other
)

/// Unwrap Task[T] to T for async function return types
let unwrapTaskType (returnType: Expression) : Expression =
match returnType with
| Subscript { Slice = innerType } -> innerType
| _ -> returnType


/// Expression builders with automatic statement threading
[<RequireQualifiedAccess>]
Expand Down
Loading
Loading