From b937a60776f9751fe14fa3fafab158ee6aa53352 Mon Sep 17 00:00:00 2001 From: caroott Date: Thu, 16 Jul 2026 15:17:56 +0200 Subject: [PATCH 1/2] fix(python): make type and member references match their declared names --- src/Fable.Transforms/FSharp2Fable.Util.fs | 11 ++++++++++- .../Python/Fable2Python.Transforms.fs | 5 +++-- tests/Python/Misc/Util2.fs | 5 +++++ tests/Python/TestMisc.fs | 5 +++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 8899c69825..9698c2b645 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -676,7 +676,11 @@ module Helpers = let sanitizedName = match com.Options.Language with - | Python -> Fable.Py.Naming.sanitizeIdent Fable.Py.Naming.pyBuiltins.Contains name part + | Python -> + // Python classes are declared with PascalCase names (PEP 8), so entity + // references must use the same casing as the declaration + let name = Fable.Py.Naming.toPascalCase name + Fable.Py.Naming.sanitizeIdent Fable.Py.Naming.pyBuiltins.Contains name part | Rust -> (entityName |> cleanNameAsRustIdentifier) | Dart -> Naming.sanitizeDartIdent (fun _ -> false) name part | _ -> Naming.sanitizeJsIdent (fun _ -> false) name part @@ -768,7 +772,12 @@ module Helpers = | Some _ -> name | _ -> Fable.Py.Naming.toPythonNaming name + // Reference sites re-case the full composed name, so apply the naming + // convention to the composed name here as well. Otherwise a lowercase + // member name with an overload suffix is declared with the suffix + // preserved but referenced with it snake_cased Fable.Py.Naming.sanitizeIdent Fable.Py.Naming.pyBuiltins.Contains name part + |> Fable.Py.Naming.toPythonNaming | Rust -> Naming.buildNameWithoutSanitation name part | Dart -> Naming.sanitizeDartIdent (fun _ -> false) name part | _ -> Naming.sanitizeJsIdent (fun _ -> false) name part diff --git a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs index bbf70e0ba3..42fee25491 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs @@ -3355,7 +3355,7 @@ let declareDataClassType |> List.filter (fun genArg -> match genArg with | Fable.DeclaredType({ FullName = fullName }, _) -> - Helpers.removeNamespace (fullName) <> entName + Naming.toPascalCase (Helpers.removeNamespace fullName) <> entName | _ -> true ) @@ -3628,7 +3628,8 @@ let declareClassType int.GenericArgs |> List.filter (fun genArg -> match genArg with - | Fable.DeclaredType({ FullName = fullName }, _) -> Helpers.removeNamespace (fullName) <> entName + | Fable.DeclaredType({ FullName = fullName }, _) -> + Naming.toPascalCase (Helpers.removeNamespace fullName) <> entName | _ -> true ) diff --git a/tests/Python/Misc/Util2.fs b/tests/Python/Misc/Util2.fs index ac1a80e3b0..3ad2675353 100644 --- a/tests/Python/Misc/Util2.fs +++ b/tests/Python/Misc/Util2.fs @@ -12,6 +12,11 @@ type Helper3(i: int) = type H = Helper3 +// Lowercase type name at the root of the file: the Python class declaration and +// its references from other files must agree on the name +type shape(size: int) = + member _.Size = size + module Extensions = type String with diff --git a/tests/Python/TestMisc.fs b/tests/Python/TestMisc.fs index 9dcdad824d..556b16bd90 100644 --- a/tests/Python/TestMisc.fs +++ b/tests/Python/TestMisc.fs @@ -958,6 +958,11 @@ let ``test Accessing members with same name as module works`` () = let ``test Naming values with same name as module works`` () = equal 30 Same.Same.shouldEqual30 +[] +let ``test Lowercase type name from another file works`` () = + let s = Util2.shape (4) + s.Size |> equal 4 + [] let ``test Can access nested recursive function with mangled name`` () = Util.Bar.nestedRecursive 3 |> equal 10 From be154095ec7cfc3b0ab9165c213f23d9c3d6d963 Mon Sep 17 00:00:00 2001 From: caroott Date: Thu, 16 Jul 2026 15:18:30 +0200 Subject: [PATCH 2/2] fix(python): use the aliased import name in type annotations --- .../Python/Fable2Python.Annotation.fs | 21 ++++++++++++------- tests/Python/Misc/Util2.fs | 2 ++ tests/Python/TestMisc.fs | 8 +++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Python/Fable2Python.Annotation.fs b/src/Fable.Transforms/Python/Fable2Python.Annotation.fs index 3650625fde..d8d4de8b50 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Annotation.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Annotation.fs @@ -727,13 +727,20 @@ let makeEntityTypeAnnotation com ctx (entRef: Fable.EntityRef) genArgs repeatedG // 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 - | _ -> () + // Import the type if it's from another file. The import may be aliased when the + // name clashes with a local declaration, so use the returned local identifier + let annotationName = + if ent.IsFSharpUnion then + match ent.Ref.SourcePath with + | Some path when path <> com.CurrentFile -> + let importPath = Path.getRelativeFileOrDirPath false com.CurrentFile false path + + match com.GetImportExpr(ctx, importPath, annotationName) with + | Expression.Name { Id = Identifier localId } -> localId + | _ -> annotationName + | _ -> annotationName + else + annotationName makeGenericTypeAnnotation com ctx annotationName genArgs repeatedGenerics, stmts // TODO: Resolve references to types in nested modules diff --git a/tests/Python/Misc/Util2.fs b/tests/Python/Misc/Util2.fs index 3ad2675353..8cbeb66a5a 100644 --- a/tests/Python/Misc/Util2.fs +++ b/tests/Python/Misc/Util2.fs @@ -17,6 +17,8 @@ type H = Helper3 type shape(size: int) = member _.Size = size +type Direction = | Up + module Extensions = type String with diff --git a/tests/Python/TestMisc.fs b/tests/Python/TestMisc.fs index 556b16bd90..219bfbac7a 100644 --- a/tests/Python/TestMisc.fs +++ b/tests/Python/TestMisc.fs @@ -963,6 +963,14 @@ let ``test Lowercase type name from another file works`` () = let s = Util2.shape (4) s.Size |> equal 4 +[] +type Direction() = + static member Up = Util2.Direction.Up + +[] +let ``test Type with same name as type from another file works`` () = + Direction.Up |> equal Util2.Direction.Up + [] let ``test Can access nested recursive function with mangled name`` () = Util.Bar.nestedRecursive 3 |> equal 10