diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index d795186224..0c44a858c2 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* [Python] Fix derived classes of generic abstract classes not being instantiable due to mismatched mangled method names between abstract stubs and overrides (by @dbrattli) * [Dart/Rust] Fix `ResizeArray` reference equality (by @ncave) * [JS/TS/Python/Beam] Fix `ResizeArray` (`System.Collections.Generic.List`) equality to use reference equality instead of structural equality (fixes #3718) * [Beam] Fix `List.Cons` call replacement and test (by @ncave) diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 922d9ce004..9806792bb6 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* [Python] Fix derived classes of generic abstract classes not being instantiable due to mismatched mangled method names between abstract stubs and overrides (by @dbrattli) * [Dart/Rust] Fix `ResizeArray` reference equality (by @ncave) * [JS/TS/Python/Beam] Fix `ResizeArray` (`System.Collections.Generic.List`) equality to use reference equality instead of structural equality (fixes #3718) * [Beam] Fix `List.Cons` call replacement and test (by @ncave) diff --git a/src/Fable.Transforms/Python/Fable2Python.Bases.fs b/src/Fable.Transforms/Python/Fable2Python.Bases.fs index fbcbea54ce..33ebb8a99d 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Bases.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Bases.fs @@ -335,12 +335,14 @@ module InterfaceNaming = |> OverloadSuffix.getHash entityGenericParameters /// Generates a mangled member name for interfaces/abstract classes. - /// Format: EntityFullPath_MemberName + OverloadSuffix (dots replaced with underscores) + /// Format: EntityFullName_MemberName + OverloadSuffix (dots and backticks replaced with underscores). + /// Uses the entity's FullName (which includes generic arity like ``2``) so the mangled name + /// matches the one produced for overrides in derived classes. let getMangledMemberName (ent: Fable.Entity) (memb: Fable.MemberFunctionOrValue) = let overloadSuffix = getOverloadSuffix ent memb - let lastDotIndex = memb.FullName.LastIndexOf '.' - let fullNamePath = memb.FullName.Substring(0, lastDotIndex) - $"%s{fullNamePath}.%s{memb.CompiledName}%s{overloadSuffix}".Replace(".", "_") + + $"%s{ent.FullName}.%s{memb.CompiledName}%s{overloadSuffix}" + |> Fable.Py.Naming.cleanNameAsPyIdentifier /// Generates abstract method stubs for abstract classes. /// Creates stubs for dispatch slots (abstract members) that don't have default implementations. diff --git a/tests/Python/TestType.fs b/tests/Python/TestType.fs index 3d4e759647..663a3649ed 100644 --- a/tests/Python/TestType.fs +++ b/tests/Python/TestType.fs @@ -591,6 +591,22 @@ type AbstractClassWithResizeArrayProp() = type ConcreteClass1() = inherit MangledAbstractClass5(2) +// Generic abstract class with members - regression test for Python name-mangling +// where abstract stubs and overrides produced mismatched names for generic arities. +[] +type GenericAbstractRunner<'A, 'B>() = + abstract Encode: 'A -> string + abstract Decode: string -> 'A + abstract MapAToB: 'A -> 'B + abstract SomeProp: int with get + +type ConcreteGenericRunner() = + inherit GenericAbstractRunner() + override _.Encode(a: int) = string a + override _.Decode(s: string) = int s + override _.MapAToB(a: int) = string a + override _.SomeProp = 42 + type IndexedProps(v: int) = let mutable v = v 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`` () = top.B.A |> equal 0 top.B.B |> equal false +[] +let ``test Generic abstract class can be instantiated through derived class`` () = + let runner = ConcreteGenericRunner() :> GenericAbstractRunner + runner.Encode 7 |> equal "7" + runner.Decode "42" |> equal 42 + runner.MapAToB 3 |> equal "3" + runner.SomeProp |> equal 42 + [] let ``test Abstract class property backed by captured variable in object expression works`` () = let warnings = ResizeArray()