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
24 changes: 2 additions & 22 deletions src/Fable.Transforms/Python/Fable2Python.Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -104,30 +104,9 @@ let private transformUnionReflectionInfo com ctx r (ent: Fable.Entity) generics

let py, stmts = pyConstructor com ctx ent

// Generate case constructors list for make_union
// Use full case class names (UnionName_CaseName) to match the generated classes,
// except for library types (Result, Choice) which use simple names
let usesSimpleNames = Util.usesSimpleCaseNames ent.FullName

// Get the entity declaration name (with module scope) for consistent naming
let entityDeclName = FSharp2Fable.Helpers.getEntityDeclarationName com ent.Ref

let caseConstructors =
ent.UnionCases
|> Seq.map (fun uci ->
let caseName =
match uci.CompiledName with
| Some cname -> cname
| None -> uci.Name

let caseClassName =
if usesSimpleNames then
caseName
else
$"%s{entityDeclName}_%s{caseName}"

com.GetIdentifierAsExpr(ctx, caseClassName)
)
|> Seq.map (Util.getUnionCaseRef com ctx ent.Ref ent)
|> Seq.toList
|> Expression.list

Expand Down Expand Up @@ -237,6 +216,7 @@ let transformTypeInfo (com: IPythonCompiler) ctx r (genMap: Map<string, Expressi
| Replacements.Util.BclDateTimeOffset
| Replacements.Util.BclDateOnly
| Replacements.Util.BclTimeOnly
| Replacements.Util.BclRune
| Replacements.Util.BclTimer -> genericEntity fullName [], []
| Replacements.Util.BclHashSet gen
| Replacements.Util.FSharpSet gen ->
Expand Down
55 changes: 0 additions & 55 deletions src/Fable.Transforms/Python/Fable2Python.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -111,65 +111,10 @@ let getMemberArgsAndBody (com: IPythonCompiler) ctx kind hasSpread (args: Fable.

args, body, returnType

let getUnionCaseName (uci: Fable.UnionCase) =
match uci.CompiledName with
| Some cname -> cname
| None -> uci.Name

/// Attribute holding the precomputed singleton instance of a zero-field union case (see transformUnion).
let unionCaseSingletonAttrName = "singleton"
let unionCaseSingletonAttr = Identifier unionCaseSingletonAttrName

/// Gets the unique case class name by prefixing with the union type name.
/// This prevents collisions when different union types have cases with the same name.
/// Library types (Result, Choice) use simple case names without prefix.
/// The optional entityName parameter should be the compiled entity name (with module scope).
let getUnionCaseClassName
(com: IPythonCompiler)
(ent: Fable.Entity)
(uci: Fable.UnionCase)
(entityName: string option)
=
let caseName = getUnionCaseName uci
// Library types use simple names (Ok, Error, Choice1Of2, etc.) for backwards compatibility
if usesSimpleCaseNames ent.FullName then
caseName
else
// Use provided entity name or compute from entity reference
let unionName =
match entityName with
| Some name -> name
| None -> FSharp2Fable.Helpers.getEntityDeclarationName com ent.Ref

$"%s{unionName}_%s{caseName}"

/// Resolves the expression referring to a union case's class (constructor / type),
/// handling both library types (Result, Choice - simple names from fable_library)
/// and user-defined unions (full case class name, imported from another module if needed).
let getUnionCaseRef (com: IPythonCompiler) ctx (entRef: Fable.EntityRef) (ent: Fable.Entity) (uci: Fable.UnionCase) =
if isLibraryUnionType entRef.FullName then
let caseName = getUnionCaseName uci
// Result uses "result" module, Choice uses "choice" module
let moduleName =
if entRef.FullName = Types.result then
"result"
else
"choice"

libValue com ctx moduleName caseName
else
// User-defined union - use full case class name (UnionName_CaseName)
let caseClassName = getUnionCaseClassName com ent uci None

match entRef.SourcePath with
| Some path when path <> com.CurrentFile ->
// Import from another module
let importPath = Path.getRelativeFileOrDirPath false com.CurrentFile false path
com.GetImportExpr(ctx, importPath, caseClassName)
| _ ->
// Local - just get identifier
com.GetIdentifierAsExpr(ctx, caseClassName)

let getUnionExprTag (com: IPythonCompiler) ctx r (fableExpr: Fable.Expr) =
Expression.withStmts {
let! expr = com.TransformAsExpr(ctx, fableExpr)
Expand Down
47 changes: 47 additions & 0 deletions src/Fable.Transforms/Python/Fable2Python.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,53 @@ module Util =
| "FSharp.Core.FSharpResult`2" -> true
| _ -> false

let getUnionCaseName (uci: Fable.UnionCase) =
match uci.CompiledName with
| Some cname -> cname
| None -> uci.Name

let getUnionCaseClassName
(com: IPythonCompiler)
(ent: Fable.Entity)
(uci: Fable.UnionCase)
(entityName: string option)
=
let caseName = getUnionCaseName uci

if usesSimpleCaseNames ent.FullName then
caseName
else
let unionName =
match entityName with
| Some name -> name
| None -> FSharp2Fable.Helpers.getEntityDeclarationName com ent.Ref

$"%s{unionName}_%s{caseName}"

let getUnionCaseRef
(com: IPythonCompiler)
ctx
(entRef: Fable.EntityRef)
(ent: Fable.Entity)
(uci: Fable.UnionCase)
=
if isLibraryUnionType entRef.FullName then
let moduleName =
if entRef.FullName = Types.result then
"result"
else
"choice"

libValue com ctx moduleName (getUnionCaseName uci)
else
let caseClassName = getUnionCaseClassName com ent uci None

match entRef.SourcePath with
| Some path when path <> com.CurrentFile ->
let importPath = Path.getRelativeFileOrDirPath false com.CurrentFile false path
com.GetImportExpr(ctx, importPath, caseClassName)
| _ -> com.GetIdentifierAsExpr(ctx, caseClassName)

/// Ensures a statement list is non-empty by adding Pass if needed.
/// Python requires at least one statement in function/class/match bodies.
let ensureNonEmptyBody stmts =
Expand Down
1 change: 1 addition & 0 deletions tests/Python/Fable.Tests.Python.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<Compile Include="TestQueue.fs" />
<Compile Include="TestRecordType.fs" />
<Compile Include="TestReflection.fs" />
<Compile Include="TestReflectionUnionImports.fs" />
<Compile Include="TestResizeArray.fs" />
<Compile Include="TestResult.fs" />
<Compile Include="TestSeq.fs" />
Expand Down
19 changes: 19 additions & 0 deletions tests/Python/TestReflectionUnionImports.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Fable.Tests.ReflectionUnionImports

open FSharp.Reflection
open Util.Testing

type RecordWithResult = { Value: Result<int, string> }

let getGenericTypeArguments (typ: System.Type) = typ.GenericTypeArguments

[<Fact>]
let ``test FSharp.Reflection: record field Result imports its case constructors`` () =
let field = FSharpType.GetRecordFields(typeof<RecordWithResult>).[0]
field.PropertyType |> equal typeof<Result<int, string>>

[<Fact>]
let ``test FSharp.Reflection: Choice imports its case constructors`` () =
getGenericTypeArguments typeof<Choice<int, string>>
|> Array.length
|> equal 2
Loading