diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 9698c2b64..ed89a7689 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -767,15 +767,24 @@ module Helpers = match com.Options.Language with | Python -> let name = - // Don't apply Python naming convention if member has compiled name attribute + // Don't snake_case the prefix for [] members. This is + // largely subsumed by the re-casing of the whole composed name below, + // but keeps the intent explicit at this step. match memb.Attributes |> Helpers.tryFindAttrib Atts.compiledName with | 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 + // Import/reference sites re-case the full composed name unconditionally + // (see the imports printer in Fable2Python.Transforms), so re-case it here + // too to keep declarations and references in agreement. This is what makes + // a lowercase member with an overload suffix, e.g. `foo__ctor_Z`, be + // both declared and imported as `foo__ctor_z`. + // + // Caveat: being unconditional, this also overrides the [] skip + // above when the composed name has no uppercase entity/module prefix, i.e. a + // root-level compiled name starting lowercase is snake_cased. Prefixed names + // (class / nested-module members) keep their uppercase prefix, so toPythonNaming + // is a no-op there and the compiled name survives. Fable.Py.Naming.sanitizeIdent Fable.Py.Naming.pyBuiltins.Contains name part |> Fable.Py.Naming.toPythonNaming | Rust -> Naming.buildNameWithoutSanitation name part diff --git a/tests/Python/Misc/Util2.fs b/tests/Python/Misc/Util2.fs index 8cbeb66a5..1d76e7237 100644 --- a/tests/Python/Misc/Util2.fs +++ b/tests/Python/Misc/Util2.fs @@ -15,6 +15,10 @@ 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) = + // Overloaded constructors are emitted as module-level names with an overload + // suffix (e.g. `shape__ctor_Z`) and imported across files. The declaration + // and the import must agree on the suffix casing too, not just the base name. + new(label: string) = shape(label.Length) member _.Size = size type Direction = | Up diff --git a/tests/Python/TestMisc.fs b/tests/Python/TestMisc.fs index 219bfbac7..973873b61 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 +[] +let ``test Overloaded constructor on lowercase type from another file works`` () = + // Exercises the `shape__ctor_Z` overload-suffix casing across files + let s1 = Util2.shape (4) + let s2 = Util2.shape ("abc") + s1.Size |> equal 4 + s2.Size |> equal 3 + [] type Direction() = static member Up = Util2.Direction.Up