Skip to content

Commit 00c8542

Browse files
dbrattliclaude
andauthored
fix: [Python] consistent mangled names for generic abstract class members (#4544)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8f2af21 commit 00c8542

4 files changed

Lines changed: 32 additions & 4 deletions

File tree

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Fix derived classes of generic abstract classes not being instantiable due to mismatched mangled method names between abstract stubs and overrides (by @dbrattli)
1213
* [Dart/Rust] Fix `ResizeArray` reference equality (by @ncave)
1314
* [JS/TS/Python/Beam] Fix `ResizeArray` (`System.Collections.Generic.List`) equality to use reference equality instead of structural equality (fixes #3718)
1415
* [Beam] Fix `List.Cons` call replacement and test (by @ncave)

src/Fable.Compiler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Fix derived classes of generic abstract classes not being instantiable due to mismatched mangled method names between abstract stubs and overrides (by @dbrattli)
1213
* [Dart/Rust] Fix `ResizeArray` reference equality (by @ncave)
1314
* [JS/TS/Python/Beam] Fix `ResizeArray` (`System.Collections.Generic.List`) equality to use reference equality instead of structural equality (fixes #3718)
1415
* [Beam] Fix `List.Cons` call replacement and test (by @ncave)

src/Fable.Transforms/Python/Fable2Python.Bases.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,14 @@ module InterfaceNaming =
335335
|> OverloadSuffix.getHash entityGenericParameters
336336

337337
/// Generates a mangled member name for interfaces/abstract classes.
338-
/// Format: EntityFullPath_MemberName + OverloadSuffix (dots replaced with underscores)
338+
/// Format: EntityFullName_MemberName + OverloadSuffix (dots and backticks replaced with underscores).
339+
/// Uses the entity's FullName (which includes generic arity like ``2``) so the mangled name
340+
/// matches the one produced for overrides in derived classes.
339341
let getMangledMemberName (ent: Fable.Entity) (memb: Fable.MemberFunctionOrValue) =
340342
let overloadSuffix = getOverloadSuffix ent memb
341-
let lastDotIndex = memb.FullName.LastIndexOf '.'
342-
let fullNamePath = memb.FullName.Substring(0, lastDotIndex)
343-
$"%s{fullNamePath}.%s{memb.CompiledName}%s{overloadSuffix}".Replace(".", "_")
343+
344+
$"%s{ent.FullName}.%s{memb.CompiledName}%s{overloadSuffix}"
345+
|> Fable.Py.Naming.cleanNameAsPyIdentifier
344346

345347
/// Generates abstract method stubs for abstract classes.
346348
/// Creates stubs for dispatch slots (abstract members) that don't have default implementations.

tests/Python/TestType.fs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,22 @@ type AbstractClassWithResizeArrayProp() =
591591
type ConcreteClass1() =
592592
inherit MangledAbstractClass5(2)
593593

594+
// Generic abstract class with members - regression test for Python name-mangling
595+
// where abstract stubs and overrides produced mismatched names for generic arities.
596+
[<AbstractClass>]
597+
type GenericAbstractRunner<'A, 'B>() =
598+
abstract Encode: 'A -> string
599+
abstract Decode: string -> 'A
600+
abstract MapAToB: 'A -> 'B
601+
abstract SomeProp: int with get
602+
603+
type ConcreteGenericRunner() =
604+
inherit GenericAbstractRunner<int, string>()
605+
override _.Encode(a: int) = string a
606+
override _.Decode(s: string) = int s
607+
override _.MapAToB(a: int) = string a
608+
override _.SomeProp = 42
609+
594610
type IndexedProps(v: int) =
595611
let mutable v = v
596612
member _.Item with get (v2: int) = v + v2 and set v2 (s: string) = v <- v2 + int s
@@ -1794,6 +1810,14 @@ let ``test Unchecked.defaultof works for fields on structs`` () =
17941810
top.B.A |> equal 0
17951811
top.B.B |> equal false
17961812

1813+
[<Fact>]
1814+
let ``test Generic abstract class can be instantiated through derived class`` () =
1815+
let runner = ConcreteGenericRunner() :> GenericAbstractRunner<int, string>
1816+
runner.Encode 7 |> equal "7"
1817+
runner.Decode "42" |> equal 42
1818+
runner.MapAToB 3 |> equal "3"
1819+
runner.SomeProp |> equal 42
1820+
17971821
[<Fact>]
17981822
let ``test Abstract class property backed by captured variable in object expression works`` () =
17991823
let warnings = ResizeArray<string>()

0 commit comments

Comments
 (0)