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 src/Fable.Transforms/Python/Fable2Python.Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ let private transformRecordReflectionInfo com ctx r (ent: Fable.Entity) generics

let name =
if Util.shouldUseRecordFieldNaming ent then
fi.Name |> Naming.toRecordFieldSnakeCase |> Helpers.clean
fi.Name |> Naming.toFieldSnakeCase |> Helpers.clean
else
fi.Name |> Naming.toSnakeCase |> Helpers.clean

Expand Down
19 changes: 10 additions & 9 deletions src/Fable.Transforms/Python/Fable2Python.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3223,7 +3223,7 @@ let getUnionFieldsAsIdents (_com: IPythonCompiler) _ctx (_ent: Fable.Entity) =
let getEntityFieldsAsIdents (com: IPythonCompiler) (ent: Fable.Entity) =
let entityNamingConvention =
if shouldUseRecordFieldNaming ent then
Naming.toRecordFieldSnakeCase
Naming.toFieldSnakeCase
else
Naming.toPythonNaming

Expand All @@ -3248,7 +3248,7 @@ let getEntityFieldsAsProps (com: IPythonCompiler) ctx (ent: Fable.Entity) =
else
let namingConvention =
if shouldUseRecordFieldNaming ent then
Naming.toRecordFieldSnakeCase
Naming.toFieldSnakeCase
else
Naming.toPythonNaming

Expand Down Expand Up @@ -3290,7 +3290,7 @@ let declareDataClassType
consArgs.Args.[i].Arg
else
// Fallback to field name if consArgs doesn't have enough args
com.GetIdentifier(ctx, field.Name |> Naming.toRecordFieldSnakeCase |> Helpers.clean)
com.GetIdentifier(ctx, field.Name |> Naming.toFieldSnakeCase |> Helpers.clean)

// Uncurry lambda types for field annotations since fields store uncurried functions
let fieldType =
Expand Down Expand Up @@ -3943,7 +3943,7 @@ let transformAttachedProperty
// Apply the same naming convention as record fields for record types
let propertyName =
//if shouldUseRecordFieldNaming ent then
// memb.Name |> Naming.toRecordFieldSnakeCase |> Helpers.clean
// memb.Name |> Naming.toFieldSnakeCase |> Helpers.clean
//else
memb.Name |> Naming.toPropertyNaming

Expand Down Expand Up @@ -4097,9 +4097,10 @@ let transformUnion (com: IPythonCompiler) ctx (ent: Fable.Entity) (entName: stri
let fieldAnnotations =
uci.UnionCaseFields
|> List.map (fun field ->
// Convert to snake_case and clean to remove invalid characters like apostrophes
// Handles: "Item" -> "item", "Item1" -> "item1", "MyField" -> "my_field"
let fieldName = field.Name |> Naming.toSnakeCase |> Helpers.clean
// toFieldSnakeCase appends '_' to camelCase names ("name" -> "name_") so a
// field can't collide with the inherited `Union.name` property, which @dataclass
// would treat as a default value ("non-default argument follows default"). See #4645.
let fieldName = field.Name |> Naming.toFieldSnakeCase |> Helpers.clean
// Uncurry lambda types for field annotations since union case fields
// store uncurried functions at runtime (same as record fields)
let fieldType =
Expand Down Expand Up @@ -4238,7 +4239,7 @@ let transformClassWithCompilerGeneratedConstructor
|> List.collecti (fun i field ->
let fieldName =
if shouldUseRecordFieldNaming ent then
field.Name |> Naming.toRecordFieldSnakeCase |> Helpers.clean
field.Name |> Naming.toFieldSnakeCase |> Helpers.clean
else
match Util.getFieldNamingKind com field.FieldType field.Name with
| InstancePropertyBacking -> field.Name |> Naming.toPropertyBackingFieldNaming
Expand Down Expand Up @@ -4680,7 +4681,7 @@ let transformStaticProperty
// printfn "transformStaticProperty: %A" propName
let propertyName =
if shouldUseRecordFieldNaming ent then
propName |> Naming.toRecordFieldSnakeCase |> Helpers.clean
propName |> Naming.toFieldSnakeCase |> Helpers.clean
else
propName |> Naming.toPropertyNaming

Expand Down
6 changes: 3 additions & 3 deletions src/Fable.Transforms/Python/Fable2Python.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -463,13 +463,13 @@ module Util =

name

/// Determines if we should use the special record field naming convention (toRecordFieldSnakeCase)
/// Determines if we should use the special field naming convention (toFieldSnakeCase)
/// for the given entity. Returns true for user-defined F# records, false for built-in types.
let shouldUseRecordFieldNaming (ent: Fable.Entity) =
ent.IsFSharpRecord
&& not (ent.FullName.StartsWith("Microsoft.FSharp.Core", StringComparison.Ordinal))

/// Determines if we should use the special record field naming convention (toRecordFieldSnakeCase)
/// Determines if we should use the special field naming convention (toFieldSnakeCase)
/// for the given entity reference. Returns true for user-defined F# records, false for built-in types.
let shouldUseRecordFieldNamingForRef (entityRef: Fable.EntityRef) (ent: Fable.Entity) =
ent.IsFSharpRecord
Expand Down Expand Up @@ -510,7 +510,7 @@ module Util =
| Fable.DeclaredType(entityRef, _) ->
match com.TryGetEntity entityRef with
| Some ent when shouldUseRecordFieldNamingForRef entityRef ent && isRecordField ent fieldName ->
fieldName |> Naming.toRecordFieldSnakeCase |> Helpers.clean
fieldName |> Naming.toFieldSnakeCase |> Helpers.clean
| _ -> fieldName |> Naming.toPythonNaming // Fallback to Python naming for other types
| _ -> fieldName |> Naming.toPropertyNaming

Expand Down
5 changes: 3 additions & 2 deletions src/Fable.Transforms/Python/Prelude.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ module Naming =
let toSnakeCase (name: string) =
Naming.applyCaseRule CaseRules.SnakeCase name

/// Convert F# record field name to snake_case with special handling for camelCase/PascalCase conflicts.
/// Convert an F# field name (record or union case field) to snake_case with special handling
/// for camelCase/PascalCase conflicts.
/// - If the name is PascalCase, convert to snake_case without suffix.
/// - If the name is camelCase, convert to snake_case and add '_' suffix to avoid conflict with PascalCase.
let toRecordFieldSnakeCase (name: string) =
let toFieldSnakeCase (name: string) =
let snakeCase = Naming.applyCaseRule CaseRules.SnakeCase name

if name.Length > 0 && Char.IsLower(name.[0]) then
Expand Down
14 changes: 14 additions & 0 deletions tests/Python/TestUnionType.fs
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,17 @@ type S = S of string
let ``test sprintf formats strings cases correctly`` () =
let s = sprintf "%A" (S "1")
s |> equal "S \"1\""

// See https://github.com/fable-compiler/Fable/issues/4645
// A named union case field called `name` collided with the inherited `Union.name`
// property, which Python's @dataclass treated as a default value.
type NamedFieldUnion =
| NamedCase of name: string * optional: string

[<Fact>]
let ``test Named union case fields work when a field is called name`` () =
let value = NamedCase("v1", "v2")
match value with
| NamedCase(name, optional) ->
name |> equal "v1"
optional |> equal "v2"
Loading