Skip to content

Commit ec84204

Browse files
committed
[Python] Type annotation fixes
1 parent ba03f4a commit ec84204

File tree

3 files changed

+105
-25
lines changed

3 files changed

+105
-25
lines changed

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ open Fable.Transforms.Python.AST
1212
open Fable.Transforms.Python.Types
1313
open Fable.Transforms.Python.Util
1414

15-
/// Check if type is an inref (in-reference) or Any type.
15+
/// Check if type is an inref wrapping a struct/value type.
1616
/// In F#, struct instance method's `this` parameter is represented as inref<StructType>,
1717
/// but in Python the struct is passed directly, not wrapped in FSharpRef.
18-
let isInRefOrAnyType (com: IPythonCompiler) =
18+
/// For regular inref<T> parameters (where T is a primitive or non-struct), we keep FSharpRef.
19+
let isStructInRefType (com: IPythonCompiler) =
1920
function
20-
| Replacements.Util.IsInRefType com _ -> true
21+
| Replacements.Util.IsInRefType com innerType ->
22+
match innerType with
23+
| Fable.DeclaredType(entRef, _) ->
24+
let ent = com.GetEntity(entRef)
25+
ent.IsValueType
26+
| _ -> false
2127
| Fable.Any -> true
2228
| _ -> false
2329

@@ -698,9 +704,10 @@ let makeBuiltinTypeAnnotation com ctx typ repeatedGenerics kind =
698704
match kind with
699705
| Replacements.Util.BclGuid -> stdlibModuleTypeHint com ctx "uuid" "UUID" [] repeatedGenerics
700706
| Replacements.Util.FSharpReference genArg ->
701-
// In F#, struct instance method's `this` parameter is represented as inref<StructType>,
707+
// For struct instance methods, `this` is represented as inref<StructType> in F#,
702708
// but in Python the struct is passed directly, not wrapped in FSharpRef.
703-
if isInRefOrAnyType com typ then
709+
// For regular byref/inref/outref parameters, we use FSharpRef.
710+
if isStructInRefType com typ then
704711
typeAnnotation com ctx repeatedGenerics genArg
705712
else
706713
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics

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

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ let transformImport (com: IPythonCompiler) ctx (_r: SourceLocation option) (name
7373

7474
com.GetImportExpr(ctx, moduleName, name) |> getParts com ctx parts
7575

76-
let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.Ident list) (body: Fable.Expr) =
76+
let getMemberArgsAndBodyCore
77+
(com: IPythonCompiler)
78+
ctx
79+
kind
80+
hasSpread
81+
(args: Fable.Ident list)
82+
(body: Fable.Expr)
83+
(selfName: string)
84+
=
7785
// printfn "getMemberArgsAndBody: %A" hasSpread
7886
let funcName, genTypeParams, args, body =
7987
match kind, args with
@@ -82,9 +90,8 @@ let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.
8290
Set.difference (Annotation.getGenericTypeParams [ thisArg.Type ]) ctx.ScopedTypeParams
8391

8492
let body =
85-
// TODO: If ident is not captured maybe we can just replace it with "this"
8693
if isIdentUsed thisArg.Name body then
87-
let thisKeyword = Fable.IdentExpr { thisArg with Name = "self" }
94+
let thisKeyword = Fable.IdentExpr { thisArg with Name = selfName }
8895

8996
Fable.Let(thisArg, thisKeyword, body)
9097
else
@@ -106,6 +113,9 @@ let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.
106113

107114
args, body, returnType
108115

116+
let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.Ident list) (body: Fable.Expr) =
117+
getMemberArgsAndBodyCore com ctx kind hasSpread args body "self"
118+
109119
let getUnionCaseName (uci: Fable.UnionCase) =
110120
match uci.CompiledName with
111121
| Some cname -> cname
@@ -365,14 +375,23 @@ let transformCast (com: IPythonCompiler) (ctx: Context) t e : Expression * State
365375

366376
// Wrap ResizeArray (Python list) when cast to IEnumerable
367377
// Python lists don't implement IEnumerable_1, so they need wrapping
378+
// Optimization: If ResizeArray was created via of_seq from IEnumerable, use the original arg
368379
| Types.ienumerableGeneric, _ when
369380
match e.Type with
370381
| Fable.Array(_, Fable.ArrayKind.ResizeArray) -> true
371382
| Fable.DeclaredType(entRef, _) when entRef.FullName = Types.resizeArray -> true
372383
| _ -> false
373384
->
374385
let listExpr, stmts = com.TransformAsExpr(ctx, e)
375-
libCall com ctx None "util" "to_enumerable" [ listExpr ], stmts
386+
// Check if the expression is of_seq(arg) - if so, use arg directly (already IEnumerable)
387+
match listExpr with
388+
| Expression.Call {
389+
Func = Expression.Name { Id = Identifier "of_seq" }
390+
Args = [ innerArg ]
391+
} ->
392+
// Skip both of_seq and to_enumerable - use original IEnumerable directly
393+
innerArg, stmts
394+
| _ -> libCall com ctx None "util" "to_enumerable" [ listExpr ], stmts
376395

377396
| _ -> com.TransformAsExpr(ctx, e)
378397
| Fable.Number(Float32, _), _ ->
@@ -601,7 +620,59 @@ let transformObjectExpr
601620
// A generic class nested in another generic class cannot use same type variables. (PEP-484)
602621
let ctx = { ctx with TypeParamsScope = ctx.TypeParamsScope + 1 }
603622

623+
// Check if any member body uses ThisValue from an outer scope (e.g., inside a constructor).
624+
// ThisValue specifically represents `self` in a constructor/method context.
625+
// Note: IsThisArgument identifiers are captured via default arguments (x: Any=x),
626+
// so we only need to handle explicit ThisValue here.
627+
let usesOuterThis =
628+
members
629+
|> List.exists (fun memb ->
630+
memb.Body
631+
|> deepExists (
632+
function
633+
| Fable.Value(Fable.ThisValue _, _) -> true
634+
| _ -> false
635+
)
636+
)
637+
638+
// Only generate capture statement if outer this is actually used.
639+
// This allows inner class methods to reference the outer instance via "_this"
640+
// while using standard "self" for the inner instance (satisfies Pylance).
641+
let thisCaptureStmts =
642+
if usesOuterThis then
643+
let anyType = stdlibModuleAnnotation com ctx "typing" "Any" []
644+
645+
[
646+
Statement.assign (Expression.name "_this", anyType, value = Expression.name "self")
647+
]
648+
else
649+
[]
650+
651+
// Replace ThisValue in the body with an identifier reference to "_this"
652+
// This ensures that outer self references correctly bind to the captured variable
653+
let replaceThisValue (body: Fable.Expr) =
654+
if usesOuterThis then
655+
body
656+
|> visitFromInsideOut (
657+
function
658+
| Fable.Value(Fable.ThisValue typ, r) ->
659+
Fable.IdentExpr
660+
{
661+
Name = "_this"
662+
Type = typ
663+
IsMutable = false
664+
IsThisArgument = false
665+
IsCompilerGenerated = true
666+
Range = r
667+
}
668+
| e -> e
669+
)
670+
else
671+
body
672+
604673
let makeMethod prop hasSpread (fableArgs: Fable.Ident list) (fableBody: Fable.Expr) decorators =
674+
let fableBody = replaceThisValue fableBody
675+
605676
let args, body, returnType =
606677
getMemberArgsAndBody com ctx (Attached(isStatic = false)) hasSpread fableArgs fableBody
607678

@@ -614,16 +685,7 @@ let transformObjectExpr
614685
com.GetIdentifier(ctx, Naming.toPythonNaming name)
615686

616687
let self = Arg.arg "self"
617-
618-
let args =
619-
match decorators with
620-
// Remove extra parameters from getters, i.e __unit=None
621-
| [ Expression.Name { Id = Identifier "property" } ] ->
622-
{ args with
623-
Args = [ self ]
624-
Defaults = []
625-
}
626-
| _ -> { args with Args = self :: args.Args }
688+
let args = { args with Args = self :: args.Args }
627689

628690
// Calculate type parameters for generic object expression methods
629691
let argTypes = fableArgs |> List.map _.Type
@@ -666,12 +728,16 @@ let transformObjectExpr
666728
| Fable.Lambda(arg, body, _) -> [ arg ], body
667729
| _ -> memb.Args, memb.Body
668730

731+
// Replace ThisValue with this_ identifier for outer self references
732+
let body = replaceThisValue body
733+
669734
let args', body', returnType =
670735
getMemberArgsAndBody com ctx (NonAttached memb.Name) false args body
671736

672737
let name = com.GetIdentifier(ctx, Naming.toPythonNaming memb.Name)
673738
let self = Arg.arg "self"
674739
let args' = { args' with Args = self :: args'.Args }
740+
675741
let argTypes = args |> List.map _.Type
676742
let typeParams = Annotation.calculateMethodTypeParams com ctx argTypes body.Type
677743

@@ -716,7 +782,7 @@ let transformObjectExpr
716782

717783
let stmt = Statement.classDef (name, body = classBody, bases = interfaces)
718784

719-
Expression.call (Expression.name name), [ stmt ] @ stmts
785+
Expression.call (Expression.name name), thisCaptureStmts @ [ stmt ] @ stmts
720786

721787

722788
let transformCallArgs

src/Fable.Transforms/Python/Replacements.fs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,11 +1728,18 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this
17281728
| ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some
17291729
| ".ctor", _, [ ExprType(Number _) ] -> makeResizeArray (getElementType t) [] |> Some
17301730
| ".ctor", _, [ ArrayOrListLiteral(vals, _) ] -> makeResizeArray (getElementType t) vals |> Some
1731-
// Use Array constructor which accepts both Iterable and IEnumerable_1
1732-
| ".ctor", _, args ->
1733-
Helper.LibCall(com, "array", "Array", t, args, ?loc = r)
1734-
|> withTag "array"
1735-
|> Some
1731+
// When a ResizeArray is cast to IEnumerable and passed to ResizeArray constructor,
1732+
// unwrap the cast since list() can handle lists directly (avoids to_enumerable wrapper)
1733+
| ".ctor", _, [ TypeCast(innerExpr, DeclaredType(ent, _)) ] when
1734+
ent.FullName = Types.ienumerableGeneric
1735+
&& match innerExpr.Type with
1736+
| Array(_, ResizeArray) -> true
1737+
| DeclaredType(entRef, _) when entRef.FullName = Types.resizeArray -> true
1738+
| _ -> false
1739+
->
1740+
Helper.GlobalCall("list", t, [ innerExpr ], ?loc = r) |> Some
1741+
// Use resize_array.of_seq to create a list from IEnumerable_1 or any iterable
1742+
| ".ctor", _, args -> Helper.LibCall(com, "resize_array", "of_seq", t, args, ?loc = r) |> Some
17361743
| "get_Item", Some ar, [ idx ] -> getExpr r t ar idx |> Some
17371744
| "set_Item", Some ar, [ idx; value ] -> setExpr r ar idx value |> Some
17381745
| "Add", Some ar, [ arg ] ->

0 commit comments

Comments
 (0)