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
84 changes: 54 additions & 30 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,18 +867,15 @@ let transformCallArgs
let hasSpread =
paramsInfo |> Option.map (fun i -> i.HasSpread) |> Option.defaultValue false

// Adapt callback annotations at generic representation boundaries.
let transformArgWithAdapters (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
elif needsUnitCallableEraseForArg sigType argExpr then
let erase = com.TransformImport(ctx, "erase_unit_callable", getLibPath com "util")
Expression.call (erase, [ expr ]), stmts
else
expr, stmts
transformArgWithAdapters com ctx sigType e
)

let args, stmts' =
match args with
Expand All @@ -874,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 ->
// Use declaration signature types to identify specialization boundaries.
args
|> List.mapi (fun i e ->
let sigType = List.tryItem i callInfo.SignatureArgTypes
transformArgWithAdapters 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 @@ -1284,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 @@ -1307,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 @@ -1319,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 @@ -2832,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 @@ -3033,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 @@ -5469,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
28 changes: 26 additions & 2 deletions src/Fable.Transforms/Python/Fable2Python.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ module Util =
| _ -> false
| _ -> false

/// Detect an erased unit callable inside a callback parameter specialized from a generic input.
let needsUnitCallableEraseForArg (expectedType: Fable.Type option) (argExpr: Fable.Expr) =
let rec mismatches expected actual =
match FableTransforms.uncurryType expected, FableTransforms.uncurryType actual with
Expand All @@ -330,7 +329,6 @@ module Util =
&& List.exists2 mismatches expectedArgs actualArgs
| _ -> false

// decision: adapt only generic-to-unit callable parameters; ordinary unit and non-unit annotations stay precise
expectedType
|> Option.exists (fun expected ->
match FableTransforms.uncurryType expected, FableTransforms.uncurryType argExpr.Type with
Expand All @@ -340,6 +338,32 @@ module Util =
| _ -> 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
31 changes: 17 additions & 14 deletions src/fable-library-py/fable_library/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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 @@ -235,13 +235,12 @@ def cont(ctx: IAsyncContext[T]) -> None:

def claim() -> None:
nonlocal settled
# invariant: settlement is claimed under the gate and user continuations run outside it
with gate:
if settled:
raise RuntimeError("A continuation was invoked more than once")
raise InvalidOperationException__ctor_Z721C83C5("A continuation was invoked more than once")
settled = True

# decision: the default represents only an erased F# unit; Python cannot express that specialization of T
# 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)
Expand All @@ -255,7 +254,6 @@ def cancelled(value: OperationCanceledError) -> None:
ctx.on_cancel(value)

def on_exception(value: Exception) -> None:
# invariant: an exception after settlement belongs to continuation code and propagates unchanged
with gate:
completed = settled
if completed:
Expand All @@ -274,13 +272,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 @@ -344,12 +340,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