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: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dev = [
"dunamai>=1.23.1,<2",
"pydantic>=2.11.7",
"attrs>=25.3.0",
"pyright>=1.1.407",
"pyright>=1.1.408",
"ty>=0.0.7",
]

Expand Down
1 change: 0 additions & 1 deletion src/Fable.Build/FableLibrary/Python.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ type BuildFableLibraryPython(?skipCore: bool) =
// Copy all Python/F# files to the build directory
Directory.GetFiles(this.LibraryDir, "*") |> Shell.copyFiles this.BuildDir
Directory.GetFiles(this.SourceDir, "*.py") |> Shell.copyFiles this.OutDir
Directory.GetFiles(this.SourceDir, "*.pyi") |> Shell.copyFiles this.OutDir

// Python extension modules
Directory.GetFiles(Path.Combine(this.SourceDir, "core"), "*")
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* [Python] Changed DU representation to use separate classes for each case (by @dbrattli)
* [Python] Fable will no longer auto-generate `__str__` or `__hash__` for custom types. Use the `Py.Stringable` and `Py.Hashable` marker interfaces to generate these methods (by @dbrattli)

### Added
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* [Python] Changed DU representation to use separate classes for each case (by @dbrattli)
* [Python] Fable will no longer auto-generate `__str__` or `__hash__` for custom types. Use the `Py.Stringable` and `Py.Hashable` marker interfaces to generate these methods (by @dbrattli)

### Added
Expand Down
50 changes: 48 additions & 2 deletions src/Fable.Transforms/Python/Fable2Python.Annotation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -373,12 +373,28 @@ let makeGenericTypeAnnotation'

Expression.subscript (name, Expression.tuple genArgs)

/// Creates a subscript expression for generic type parameters from a list of names.
/// For a single param, returns just the name; for multiple, returns a tuple.
/// E.g., [] -> baseExpr, [T] -> baseExpr[T], [T1, T2] -> baseExpr[T1, T2]
let makeGenericParamSubscript (genParamNames: string list) (baseExpr: Expression) =
if List.isEmpty genParamNames then
baseExpr
else
let genArgs = genParamNames |> List.map Expression.name

let slice =
match genArgs with
| [ single ] -> single
| multiple -> Expression.tuple multiple

Expression.subscript (baseExpr, slice)

let resolveGenerics com ctx generics repeatedGenerics : Expression list * Statement list =
generics
|> List.map (typeAnnotation com ctx repeatedGenerics)
|> Helpers.unzipArgs

let typeAnnotation
let rec typeAnnotation
(com: IPythonCompiler)
ctx
(repeatedGenerics: Set<string> option)
Expand Down Expand Up @@ -644,7 +660,36 @@ let makeEntityTypeAnnotation com ctx (entRef: Fable.EntityRef) genArgs repeatedG
| "string" -> StringTypeAnnotation
| _ -> AnyTypeAnnotation*)
| Expression.Name { Id = Identifier id } ->
makeGenericTypeAnnotation com ctx id genArgs repeatedGenerics, stmts
// For F# union types, tryPyConstructor returns the underscore-prefixed base class
// name (e.g., "_MyUnion"). For type annotations:
// - Inside base class definition: use base class name (_MyUnion)
// - Elsewhere: use type alias (MyUnion) for public API
let isInsideThisUnionBaseClass =
match ctx.EnclosingUnionBaseClass with
| Some enclosingName -> ent.DisplayName = enclosingName
| None -> false

let annotationName =
if
ent.IsFSharpUnion
&& id.StartsWith("_", StringComparison.Ordinal)
&& not isInsideThisUnionBaseClass
then
// Outside base class - use type alias (strip underscore)
id.Substring(1)
else
// Inside base class or not a union - use as-is
id

// Import the type if it's from another file
if ent.IsFSharpUnion then
match ent.Ref.SourcePath with
| Some path when path <> com.CurrentFile ->
let importPath = Path.getRelativeFileOrDirPath false com.CurrentFile false path
com.GetImportExpr(ctx, importPath, annotationName) |> ignore
| _ -> ()

makeGenericTypeAnnotation com ctx annotationName genArgs repeatedGenerics, stmts
// TODO: Resolve references to types in nested modules
| _ -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
| None -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
Expand Down Expand Up @@ -684,6 +729,7 @@ let makeBuiltinTypeAnnotation com ctx typ repeatedGenerics kind =
fableModuleAnnotation com ctx "result" "FSharpResult_2" resolved, stmts
| Replacements.Util.FSharpChoice genArgs ->
let resolved, stmts = resolveGenerics com ctx genArgs repeatedGenerics
// Use the type alias (clean name without underscore prefix)
let name = $"FSharpChoice_%d{List.length genArgs}"
fableModuleAnnotation com ctx "choice" name resolved, stmts
| _ -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
Expand Down
29 changes: 28 additions & 1 deletion src/Fable.Transforms/Python/Fable2Python.Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,34 @@ let private transformUnionReflectionInfo com ctx r (ent: Fable.Entity) generics

let py, stmts = pyConstructor com ctx ent

[ fullnameExpr; arrayExpr com ctx generics; py; cases ]
// 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.toList
|> Expression.list

[ fullnameExpr; arrayExpr com ctx generics; py; cases; caseConstructors ]
|> libReflectionCall com ctx None "union",
stmts

Expand Down
Loading
Loading