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
81 changes: 54 additions & 27 deletions src/Fable.Transforms/Python/Fable2Python.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -757,11 +757,24 @@ let transformObjectExpr
Expression.call (Expression.name name), thisCaptureStmts @ [ stmt ] @ stmts


let transformCallArgs
let transformArgWithAdapters (com: IPythonCompiler) ctx (sigType: Fable.Type option) (argExpr: Fable.Expr) =
let expr, stmts = com.TransformAsExpr(ctx, argExpr)

if needsOptionWidenForArg sigType argExpr then
let widen = com.TransformImport(ctx, "widen", getLibPath com "option")
Expression.call (widen, [ expr ]), stmts
elif needsUnitCallableEraseForArg sigType argExpr then
let erase = com.TransformImport(ctx, "erase_unit_callable", getLibPath com "util")
Expression.call (erase, [ expr ]), stmts
else
expr, stmts

let transformCallArgsWith
(com: IPythonCompiler)
ctx
(callInfo: Fable.CallInfo)
(isBaseConstructorCall: bool)
(fallbackSigTypes: Fable.Type list)
: Expression list * Keyword list * Statement list
=

Expand Down Expand Up @@ -854,15 +867,15 @@ let transformCallArgs
let hasSpread =
paramsInfo |> Option.map (fun i -> i.HasSpread) |> Option.defaultValue false

// Helper to transform an arg and wrap with widen() if needed
let transformArgWithWiden (sigType: Fable.Type option) (argExpr: Fable.Expr) =
let expr, stmts = com.TransformAsExpr(ctx, argExpr)
let transformArgs args =
args
|> List.mapi (fun i e ->
let sigType =
List.tryItem i callInfo.SignatureArgTypes
|> Option.orElse (List.tryItem i fallbackSigTypes)

if needsOptionWidenForArg sigType argExpr then
let widen = com.TransformImport(ctx, "widen", getLibPath com "option")
Expression.call (widen, [ expr ]), stmts
else
expr, stmts
transformArgWithAdapters com ctx sigType e
)

let args, stmts' =
match args with
Expand All @@ -871,31 +884,30 @@ let transformCallArgs
match List.rev args with
| [] -> [], []
| Replacements.Util.ArrayOrListLiteral(spreadArgs, _) :: rest ->
let rest = List.rev rest |> List.map (fun e -> com.TransformAsExpr(ctx, e))
let rest = List.rev rest |> transformArgs

rest @ List.map (fun e -> com.TransformAsExpr(ctx, e)) spreadArgs
|> Helpers.unzipArgs
| last :: rest ->
let rest, stmts =
List.rev rest
|> List.map (fun e -> com.TransformAsExpr(ctx, e))
|> Helpers.unzipArgs
let rest, stmts = List.rev rest |> transformArgs |> Helpers.unzipArgs

let expr, stmts' = com.TransformAsExpr(ctx, last)
rest @ [ Expression.starred expr ], stmts @ stmts'
| args ->
// Transform args with widen() where needed based on signature types
args
|> List.mapi (fun i e ->
let sigType = List.tryItem i callInfo.SignatureArgTypes
transformArgWithWiden sigType e
)
|> Helpers.unzipArgs
| args -> args |> transformArgs |> Helpers.unzipArgs

match objArg with
| None -> args, [], stmts @ stmts'
| Some objArg -> args, objArg, stmts @ stmts'

let transformCallArgs
(com: IPythonCompiler)
ctx
(callInfo: Fable.CallInfo)
(isBaseConstructorCall: bool)
: Expression list * Keyword list * Statement list
=
transformCallArgsWith com ctx callInfo isBaseConstructorCall []

let resolveExpr (ctx: Context) _t strategy pyExpr : Statement list =
// printfn "resolveExpr: %A" (pyExpr, strategy)
match strategy with
Expand Down Expand Up @@ -1281,7 +1293,8 @@ let transformCall (com: IPythonCompiler) ctx range callee (callInfo: Fable.CallI

let callee', stmts = com.TransformAsExpr(ctx, callee)

let args, kw, stmts' = transformCallArgs com ctx callInfo false
let args, kw, stmts' =
transformCallArgsWith com ctx callInfo false (calleeSignatureArgTypes ctx callee callInfo)

match callee, callInfo.ThisArg with
| Fable.Get(expr, Fable.FieldGet { Name = "Dispose" }, _, _), _ ->
Expand All @@ -1304,9 +1317,17 @@ let transformCall (com: IPythonCompiler) ctx range callee (callInfo: Fable.CallI
Expression.call (callee', args, kw, ?loc = range), stmts @ stmts'
| _, None -> callFunction range callee' args kw, stmts @ stmts'

let transformCurriedApply com ctx range (TransformExpr com ctx (applied, stmts)) args =
((applied, stmts), args)
||> List.fold (fun (applied, stmts) arg ->
let transformCurriedApply (com: IPythonCompiler) ctx range (callee: Fable.Expr) args =
let rec expectedArgTypes typ args =
match typ, args with
| Fable.LambdaType(argType, returnType), _ :: rest -> Some argType :: expectedArgTypes returnType rest
| _, _ :: rest -> None :: expectedArgTypes typ rest
| _, [] -> []

let applied, stmts = com.TransformAsExpr(ctx, callee)

((applied, stmts), List.zip (expectedArgTypes callee.Type args) args)
||> List.fold (fun (applied, stmts) (sigType, arg) ->
let args, argStmts =
match arg with
// TODO: If arg type is unit but it's an expression with potential
Expand All @@ -1316,7 +1337,7 @@ let transformCurriedApply com ctx range (TransformExpr com ctx (applied, stmts))
| Fable.Value(Fable.UnitConstant, _) -> [], []
| Fable.IdentExpr ident when ident.Type = Fable.Unit -> [], []
| arg ->
let argExpr, transformStmts = com.TransformAsExpr(ctx, arg)
let argExpr, transformStmts = transformArgWithAdapters com ctx sigType arg
// When a Call or CurriedApply result is passed as an argument,
// check if we need to erase Option[T] to T | None
let argExpr =
Expand Down Expand Up @@ -2829,6 +2850,8 @@ let rec transformAsExpr (com: IPythonCompiler) ctx (expr: Fable.Expr) : Expressi
iife com ctx expr

| Fable.LetRec(bindings, body) ->
let ctx = withDeclaredFunctionTypes ctx bindings

if ctx.HoistVars(List.map fst bindings) then
Expression.withStmts {
let values, stmts =
Expand Down Expand Up @@ -3030,10 +3053,13 @@ let rec transformAsStatements (com: IPythonCompiler) ctx returnStrategy (expr: F

| Fable.Let(ident, value, body) ->
let binding = transformBindingAsStatements com ctx ident value
let ctx = withDeclaredFunctionTypes ctx [ ident, value ]

List.append binding (transformAsStatements com ctx returnStrategy body)

| Fable.LetRec(bindings, body) ->
let ctx = withDeclaredFunctionTypes ctx bindings

let bindings =
bindings
|> Seq.collect (fun (i, v) -> transformBindingAsStatements com ctx i v)
Expand Down Expand Up @@ -5466,6 +5492,7 @@ let transformFile (com: IPythonCompiler) (file: Fable.File) =
ScopedTypeParams = Set.empty
TypeParamsScope = 0
NarrowedTypes = Map.empty
DeclaredFunctionTypes = Map.empty
EnclosingUnionBaseClass = None
InEmitMacroArgument = false
}
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Transforms/Python/Fable2Python.Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ type Context =
ScopedTypeParams: Set<string>
TypeParamsScope: int
NarrowedTypes: Map<string, Fable.Type>
DeclaredFunctionTypes: Map<string, Fable.Type>
/// When inside a union base class definition, this holds the entity name.
/// Used to determine whether to use base class name or type alias for annotations.
EnclosingUnionBaseClass: string option
Expand Down
45 changes: 45 additions & 0 deletions src/Fable.Transforms/Python/Fable2Python.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,51 @@ module Util =
| _ -> false
| _ -> false

let needsUnitCallableEraseForArg (expectedType: Fable.Type option) (argExpr: Fable.Expr) =
let rec mismatches expected actual =
match FableTransforms.uncurryType expected, FableTransforms.uncurryType actual with
| Fable.DelegateType([ Fable.GenericParam _ ], _), Fable.DelegateType([ Fable.Unit ], _) -> true
| Fable.DelegateType(expectedArgs, _), Fable.DelegateType(actualArgs, _)
| Fable.Tuple(expectedArgs, _), Fable.Tuple(actualArgs, _) ->
List.sameLength expectedArgs actualArgs
&& List.exists2 mismatches expectedArgs actualArgs
| _ -> false

expectedType
|> Option.exists (fun expected ->
match FableTransforms.uncurryType expected, FableTransforms.uncurryType argExpr.Type with
| Fable.DelegateType(expectedArgs, _), Fable.DelegateType(actualArgs, _) ->
List.sameLength expectedArgs actualArgs
&& List.exists2 mismatches expectedArgs actualArgs
| _ -> false
)

// Identifiers at call sites carry the instantiated type, not the declared generic one
let withDeclaredFunctionTypes (ctx: Context) (bindings: (Fable.Ident * Fable.Expr) list) =
let declared =
(ctx.DeclaredFunctionTypes, bindings)
||> List.fold (fun declared (ident, value) ->
match value with
| Fable.Lambda _
| Fable.Delegate _ -> Map.add ident.Name ident.Type declared
| _ -> Map.remove ident.Name declared
)

{ ctx with DeclaredFunctionTypes = declared }

// uncurryApplications emits an empty SignatureArgTypes for local function calls
let calleeSignatureArgTypes (ctx: Context) (callee: Fable.Expr) (callInfo: Fable.CallInfo) =
let calleeType =
match callee with
| Fable.IdentExpr ident ->
Map.tryFind ident.Name ctx.DeclaredFunctionTypes
|> Option.defaultValue callee.Type
| _ -> callee.Type

match callInfo.SignatureArgTypes, FableTransforms.uncurryType calleeType with
| [], Fable.DelegateType(argTypes, _) when List.sameLength argTypes callInfo.Args -> argTypes
| _ -> []

/// Wraps None values in cast(type, None) for type safety.
/// Skips if type annotation is also None (unit type).
let wrapNoneInCast (com: IPythonCompiler) ctx (value: Expression) (typeAnnotation: Expression) : Expression =
Expand Down
68 changes: 50 additions & 18 deletions src/fable-library-py/fable_library/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from threading import RLock, Timer
from typing import (
Any,
cast,
)

from .array_ import Array
Expand All @@ -33,7 +34,7 @@
)
from .event import IEvent_2
from .protocols import IEnumerable_1
from .system import TimeoutException__ctor
from .system import InvalidOperationException__ctor_Z721C83C5, TimeoutException__ctor
from .task import TaskCompletionSource
from .time_span import TimeSpan, to_milliseconds
from .types import UNIT, Unit
Expand Down Expand Up @@ -228,16 +229,42 @@ def on_error(err: Exception):
return protected_cont(cont)


def from_continuations[T](
f: Callable[
[Continuations[T]],
None,
],
) -> Callable[[IAsyncContext[T]], None]:
def from_continuations[T](f: Callable[[Continuations[T]], None]) -> Async[T]:
def cont(ctx: IAsyncContext[T]) -> None:
f((ctx.on_success, ctx.on_error, ctx.on_cancel))
settled = False
gate = RLock()

return protected_cont(cont)
def claim() -> None:
nonlocal settled
with gate:
if settled:
raise InvalidOperationException__ctor_Z721C83C5("A continuation was invoked more than once")
settled = True

# Compiled F# calls the success continuation with no argument when T is unit
def success(value: T = cast(T, None)) -> None:
claim()
ctx.on_success(value)

def error(value: Exception) -> None:
claim()
ctx.on_error(value)

def cancelled(value: OperationCanceledError) -> None:
claim()
ctx.on_cancel(value)

def on_exception(value: Exception) -> None:
with gate:
completed = settled
if completed:
raise value
error(value)

guarded = IAsyncContext.create(ctx.trampoline, ctx.cancel_token, success, on_exception, cancelled)
protected_cont(lambda _: f((success, error, cancelled)))(guarded)

return cont


def from_continuations_unit(
Expand All @@ -246,13 +273,11 @@ def from_continuations_unit(
None,
],
) -> Async[None]:
def cont(ctx: IAsyncContext[None]) -> None:
def success(value: None = None) -> None:
ctx.on_success(value)
def callback(conts: Continuations[None]) -> None:
success, error, cancelled = conts
f((lambda: success(None), error, cancelled))

f((success, ctx.on_error, ctx.on_cancel))

return protected_cont(cont)
return from_continuations(callback)


def await_task[T](task: Awaitable[T]) -> Async[T]:
Expand Down Expand Up @@ -327,12 +352,19 @@ def start_with_continuations[T](
token is used."""
trampoline = Trampoline()

def done[U](cont: Callable[[U], None]) -> Callable[[U], None]:
def terminal(value: U) -> None:
trampoline.completed = True
cont(value)

return terminal

ctx = IAsyncContext.create(
trampoline,
cancellation_token or default_cancellation_token,
continuation or empty_continuation,
exception_continuation or empty_continuation,
cancellation_continuation or empty_continuation,
done(continuation or empty_continuation),
done(exception_continuation or empty_continuation),
done(cancellation_continuation or empty_continuation),
)

def runner() -> None:
Expand Down
Loading
Loading