diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index bb3dc222bf..7e7c73fc28 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Remove support for Python 3.9. Add GH testing for Python 3.12 (by @dbrattli) * Support (un)curry up to 20 arguments (by @MangelMaxime) +* Expose discriminated union constructors to Python code (by @smoothdeveloper). #### Dart diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 0bdf708696..f5912d9dd0 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -3688,6 +3688,16 @@ module Util = let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = let fieldIds = getUnionFieldsAsIdents com ctx ent + let genTypeArgument = + let gen = + getGenericTypeParams [ fieldIds[1].Type ] + |> Set.toList + |> List.tryHead + + let ta = Expression.name (gen |> Option.defaultValue "Any") + let id = ident com ctx fieldIds[1] + Arg.arg (id, annotation = ta) + let args, isOptional = let args = fieldIds[0] @@ -3697,20 +3707,8 @@ module Util = Arg.arg (id, annotation = ta)) |> List.singleton - let varargs = - fieldIds[1] - |> ident com ctx - |> (fun id -> - let gen = - getGenericTypeParams [ fieldIds[1].Type ] - |> Set.toList - |> List.tryHead - - let ta = Expression.name (gen |> Option.defaultValue "Any") - Arg.arg (id, annotation = ta)) - let isOptional = Helpers.isOptional fieldIds - Arguments.arguments (args = args, vararg = varargs), isOptional + Arguments.arguments (args = args, vararg = genTypeArgument), isOptional let body = [ yield callSuperAsStatement [] @@ -3751,8 +3749,48 @@ module Util = Statement.functionDef (name, Arguments.arguments (), body = body, returns = returnType, decoratorList = decorators) + let constructors = + [ + for tag, case in ent.UnionCases |> Seq.indexed do + let name = Identifier case.Name + let args = + Arguments.arguments + [ + for field in case.UnionCaseFields do + let ta, _ = typeAnnotation com ctx None field.FieldType + Arg.arg(com.GetIdentifier(ctx, field.Name), ta) + ] + let decorators = [Expression.name "staticmethod"] + let values = + [ + for field in case.UnionCaseFields do + let identifier : Fable.Ident = + { Name = field.Name + Type = field.FieldType + IsMutable = false + IsThisArgument = true + IsCompilerGenerated = false + Range = None } + Fable.Expr.IdentExpr identifier + ] + let unionExpr,_ = + Fable.Value(Fable.ValueKind.NewUnion(values, tag, ent.Ref, []),None) + |> transformAsExpr com ctx + let body = + [Statement.return' unionExpr] + let returnType = + match args.VarArg with + | None -> Expression.name entName + | Some _ -> + Expression.subscript(Expression.name entName, Expression.name genTypeArgument.Arg) + Statement.functionDef(name, args, body = body, returns = returnType, decoratorList = decorators) + ] let baseExpr = libValue com ctx "types" "Union" |> Some - let classMembers = List.append [ cases ] classMembers + let classMembers = [ + cases + yield! constructors + yield! classMembers + ] declareType com ctx ent entName args isOptional body baseExpr classMembers let transformClassWithCompilerGeneratedConstructor (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: string) classMembers = diff --git a/tests/Python/TestUnionType.fs b/tests/Python/TestUnionType.fs index d581a27e0b..56d33b0f78 100644 --- a/tests/Python/TestUnionType.fs +++ b/tests/Python/TestUnionType.fs @@ -205,3 +205,18 @@ let ``test Equality works in filter`` () = |> Array.filter (fun r -> r.Case = MyUnion3.Case1) |> Array.length |> equal 2 + +#if FABLE_COMPILER +open Fable.Core +[] +let ``test constructor exposed to python code`` () = + let u0 = MyUnion.Case0 + let u1 = MyUnion.Case1 "a" + let u2 = MyUnion.Case2 ("a","b") + let u3 = MyUnion.Case3 ("a","b","c") + let v0 : MyUnion = PyInterop.emitPyExpr () "MyUnion.Case0()" + let v1 : MyUnion = PyInterop.emitPyExpr "a" "MyUnion.Case1($0)" + let v2 : MyUnion = PyInterop.emitPyExpr ("a","b") "MyUnion.Case2($0,$1)" + let v3 : MyUnion = PyInterop.emitPyExpr ("a","b","c") "MyUnion.Case3($0,$1,$2)" + equal [u0;u1;u2;u3] [v0;v1;v2;v3] +#endif