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
2 changes: 0 additions & 2 deletions pyrightconfig.ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
"exclude": [
"**/.venv/**",
"**/node_modules/**",
"temp/fable-library-py/fable_library/list.py",
"temp/tests/Python/test_applicative.py",
"temp/tests/Python/test_misc.py",
"temp/tests/Python/test_type.py",
"temp/tests/Python/fable_modules/thoth_json_python/encode.py"
]
}
32 changes: 30 additions & 2 deletions src/Fable.Transforms/Python/Fable2Python.Annotation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ let getGenericArgs (typ: Fable.Type) : Fable.Type list =
let containsGenericParams (t: Fable.Type) =
FSharp2Fable.Util.getGenParamNames [ t ] |> List.isEmpty |> not

/// Check if a type contains Option nested inside a container (Array, List, Tuple).
/// When Options are inside invariant containers, we must use Option[T] form consistently
/// to match function signatures that use generic type parameters.
let rec hasOptionInContainer (t: Fable.Type) : bool =
match t with
| Fable.Array(elementType, _) -> containsOptionType elementType
| Fable.List elementType -> containsOptionType elementType
| Fable.Tuple(genArgs, _) -> genArgs |> List.exists containsOptionType
| Fable.DeclaredType(_, genArgs) -> genArgs |> List.exists containsOptionType
| _ -> false

/// Check if a type is or contains an Option type
and containsOptionType (t: Fable.Type) : bool =
match t with
| Fable.Option _ -> true
| Fable.Array(elementType, _) -> containsOptionType elementType
| Fable.List elementType -> containsOptionType elementType
| Fable.Tuple(genArgs, _) -> genArgs |> List.exists containsOptionType
| Fable.DeclaredType(_, genArgs) -> genArgs |> List.exists containsOptionType
| _ -> false

/// Check if a type is a callable type (Lambda or Delegate)
let isCallableType (t: Fable.Type) =
match t with
Expand Down Expand Up @@ -472,6 +493,8 @@ let makeImportTypeAnnotation com ctx genArgs moduleName typeName =
let makeEntityTypeAnnotation com ctx (entRef: Fable.EntityRef) genArgs repeatedGenerics =
// printfn "DeclaredType: %A" entRef.FullName
match entRef.FullName, genArgs with
// Python's BaseException - used for catch-all exception handlers
| "BaseException", _ -> Expression.name "BaseException", []
| Types.result, _ ->
let resolved, stmts = resolveGenerics com ctx genArgs repeatedGenerics
fableModuleAnnotation com ctx "result" "FSharpResult_2" resolved, stmts
Expand Down Expand Up @@ -630,8 +653,13 @@ let makeBuiltinTypeAnnotation com ctx typ repeatedGenerics kind =
match kind with
| Replacements.Util.BclGuid -> stdlibModuleTypeHint com ctx "uuid" "UUID" [] repeatedGenerics
| Replacements.Util.FSharpReference genArg ->
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics
fableModuleAnnotation com ctx "core" "FSharpRef" resolved, stmts
// In F#, struct instance method's `this` parameter is represented as inref<StructType>,
// but in Python the struct is passed directly, not wrapped in FSharpRef.
if isInRefOrAnyType com typ then
typeAnnotation com ctx repeatedGenerics genArg
else
let resolved, stmts = resolveGenerics com ctx [ genArg ] repeatedGenerics
fableModuleAnnotation com ctx "core" "FSharpRef" resolved, stmts
(*
| Replacements.Util.BclTimeSpan -> NumberTypeAnnotation
| Replacements.Util.BclDateTime -> makeSimpleTypeAnnotation com ctx "Date"
Expand Down
40 changes: 40 additions & 0 deletions src/Fable.Transforms/Python/Fable2Python.Bases.fs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,46 @@ let generatePythonProtocolDunders (com: IPythonCompiler) ctx (classEnt: Fable.En
// a collection is either a mapping or a set, not both
mappingDunders @ mutableMappingDunders @ setDunders @ mutableSetDunders

/// Known core interfaces and their method members.
/// These interfaces are injected by the compiler and their entities may not be available.
/// Maps interface full name -> set of method member names.
let knownInterfaceMethods =
Map
[
"Fable.Core.IGenericAdder`1", set [ "GetZero"; "Add" ]
"Fable.Core.IGenericAverager`1", set [ "GetZero"; "Add"; "DivideByInt" ]
"System.Collections.Generic.IComparer`1", set [ "Compare" ]
"System.Collections.Generic.IEqualityComparer`1", set [ "Equals"; "GetHashCode" ]
]

/// All known method names from core interfaces (for untyped object expressions).
let knownInterfaceMethodNames =
knownInterfaceMethods |> Map.values |> Seq.collect id |> Set.ofSeq

/// Check if the interface member is a method (vs property).
/// Methods have parameters; properties (even returning functions) don't.
/// Used in object expression code generation to determine whether to emit
/// a method or a @property decorator.
let isInterfaceMethod (com: Compiler) (typ: Fable.Type) (memberName: string) : bool =
match typ with
| Fable.DeclaredType(entRef, _) ->
// Check known interfaces first (handles compiler-injected interfaces)
match knownInterfaceMethods.TryFind entRef.FullName with
| Some methods -> methods.Contains memberName
| None ->
// Not a known interface, try entity lookup for user-defined interfaces
match com.TryGetEntity entRef with
| Some ent ->
ent.MembersFunctionsAndValues
|> Seq.tryFind (fun m -> m.DisplayName = memberName || m.CompiledName = memberName)
|> Option.map (fun m -> m.CurriedParameterGroups |> List.exists (not << List.isEmpty))
|> Option.defaultValue false
| None -> false
| Fable.Any ->
// For untyped object expressions (compiler-injected), check known method names
knownInterfaceMethodNames.Contains memberName
| _ -> false

/// Utilities for interface and abstract class member naming.
module InterfaceNaming =
/// Computes the overload suffix for an interface/abstract class member based on parameter types.
Expand Down
Loading
Loading