Skip to content

Commit 32350df

Browse files
authored
[Python] Type annotation fixes (#4330)
1 parent 0272f7d commit 32350df

File tree

6 files changed

+578
-77
lines changed

6 files changed

+578
-77
lines changed

pyrightconfig.ci.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
"exclude": [
44
"**/.venv/**",
55
"**/node_modules/**",
6-
"temp/fable-library-py/fable_library/list.py",
76
"temp/tests/Python/test_applicative.py",
87
"temp/tests/Python/test_misc.py",
9-
"temp/tests/Python/test_type.py",
108
"temp/tests/Python/fable_modules/thoth_json_python/encode.py"
119
]
1210
}

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,27 @@ let getGenericArgs (typ: Fable.Type) : Fable.Type list =
7171
let containsGenericParams (t: Fable.Type) =
7272
FSharp2Fable.Util.getGenParamNames [ t ] |> List.isEmpty |> not
7373

74+
/// Check if a type contains Option nested inside a container (Array, List, Tuple).
75+
/// When Options are inside invariant containers, we must use Option[T] form consistently
76+
/// to match function signatures that use generic type parameters.
77+
let rec hasOptionInContainer (t: Fable.Type) : bool =
78+
match t with
79+
| Fable.Array(elementType, _) -> containsOptionType elementType
80+
| Fable.List elementType -> containsOptionType elementType
81+
| Fable.Tuple(genArgs, _) -> genArgs |> List.exists containsOptionType
82+
| Fable.DeclaredType(_, genArgs) -> genArgs |> List.exists containsOptionType
83+
| _ -> false
84+
85+
/// Check if a type is or contains an Option type
86+
and containsOptionType (t: Fable.Type) : bool =
87+
match t with
88+
| Fable.Option _ -> true
89+
| Fable.Array(elementType, _) -> containsOptionType elementType
90+
| Fable.List elementType -> containsOptionType elementType
91+
| Fable.Tuple(genArgs, _) -> genArgs |> List.exists containsOptionType
92+
| Fable.DeclaredType(_, genArgs) -> genArgs |> List.exists containsOptionType
93+
| _ -> false
94+
7495
/// Check if a type is a callable type (Lambda or Delegate)
7596
let isCallableType (t: Fable.Type) =
7697
match t with
@@ -472,6 +493,8 @@ let makeImportTypeAnnotation com ctx genArgs moduleName typeName =
472493
let makeEntityTypeAnnotation com ctx (entRef: Fable.EntityRef) genArgs repeatedGenerics =
473494
// printfn "DeclaredType: %A" entRef.FullName
474495
match entRef.FullName, genArgs with
496+
// Python's BaseException - used for catch-all exception handlers
497+
| "BaseException", _ -> Expression.name "BaseException", []
475498
| Types.result, _ ->
476499
let resolved, stmts = resolveGenerics com ctx genArgs repeatedGenerics
477500
fableModuleAnnotation com ctx "result" "FSharpResult_2" resolved, stmts
@@ -630,8 +653,13 @@ let makeBuiltinTypeAnnotation com ctx typ repeatedGenerics kind =
630653
match kind with
631654
| Replacements.Util.BclGuid -> stdlibModuleTypeHint com ctx "uuid" "UUID" [] repeatedGenerics
632655
| Replacements.Util.FSharpReference genArg ->
633-
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics
634-
fableModuleAnnotation com ctx "core" "FSharpRef" resolved, stmts
656+
// In F#, struct instance method's `this` parameter is represented as inref<StructType>,
657+
// but in Python the struct is passed directly, not wrapped in FSharpRef.
658+
if isInRefOrAnyType com typ then
659+
typeAnnotation com ctx repeatedGenerics genArg
660+
else
661+
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics
662+
fableModuleAnnotation com ctx "core" "FSharpRef" resolved, stmts
635663
(*
636664
| Replacements.Util.BclTimeSpan -> NumberTypeAnnotation
637665
| Replacements.Util.BclDateTime -> makeSimpleTypeAnnotation com ctx "Date"

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,46 @@ let generatePythonProtocolDunders (com: IPythonCompiler) ctx (classEnt: Fable.En
278278
// a collection is either a mapping or a set, not both
279279
mappingDunders @ mutableMappingDunders @ setDunders @ mutableSetDunders
280280

281+
/// Known core interfaces and their method members.
282+
/// These interfaces are injected by the compiler and their entities may not be available.
283+
/// Maps interface full name -> set of method member names.
284+
let knownInterfaceMethods =
285+
Map
286+
[
287+
"Fable.Core.IGenericAdder`1", set [ "GetZero"; "Add" ]
288+
"Fable.Core.IGenericAverager`1", set [ "GetZero"; "Add"; "DivideByInt" ]
289+
"System.Collections.Generic.IComparer`1", set [ "Compare" ]
290+
"System.Collections.Generic.IEqualityComparer`1", set [ "Equals"; "GetHashCode" ]
291+
]
292+
293+
/// All known method names from core interfaces (for untyped object expressions).
294+
let knownInterfaceMethodNames =
295+
knownInterfaceMethods |> Map.values |> Seq.collect id |> Set.ofSeq
296+
297+
/// Check if the interface member is a method (vs property).
298+
/// Methods have parameters; properties (even returning functions) don't.
299+
/// Used in object expression code generation to determine whether to emit
300+
/// a method or a @property decorator.
301+
let isInterfaceMethod (com: Compiler) (typ: Fable.Type) (memberName: string) : bool =
302+
match typ with
303+
| Fable.DeclaredType(entRef, _) ->
304+
// Check known interfaces first (handles compiler-injected interfaces)
305+
match knownInterfaceMethods.TryFind entRef.FullName with
306+
| Some methods -> methods.Contains memberName
307+
| None ->
308+
// Not a known interface, try entity lookup for user-defined interfaces
309+
match com.TryGetEntity entRef with
310+
| Some ent ->
311+
ent.MembersFunctionsAndValues
312+
|> Seq.tryFind (fun m -> m.DisplayName = memberName || m.CompiledName = memberName)
313+
|> Option.map (fun m -> m.CurriedParameterGroups |> List.exists (not << List.isEmpty))
314+
|> Option.defaultValue false
315+
| None -> false
316+
| Fable.Any ->
317+
// For untyped object expressions (compiler-injected), check known method names
318+
knownInterfaceMethodNames.Contains memberName
319+
| _ -> false
320+
281321
/// Utilities for interface and abstract class member naming.
282322
module InterfaceNaming =
283323
/// Computes the overload suffix for an interface/abstract class member based on parameter types.

0 commit comments

Comments
 (0)