From 4524806cb1034fd4ebae8ccd9059d95fc23c237e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Mar 2026 13:41:43 +0000 Subject: [PATCH] Fix super call in multi-level generic class hierarchy using wrong mangled name (fix #3895) When calling `base.Method()` in a class that inherits through a chain of generic classes, the overload hash suffix was computed incorrectly. For example, if `ProjectSelection` inherits `SelectionAspect<'Container>` which inherits `AspectBase<'C>`, calling `base.Attach(owner)` from `ProjectSelection.Attach` would generate `super["...AspectBase.Attach1505"]` but the method was defined as `"...AspectBase.Attach2B595"`, causing a runtime error. Root cause: `callAttachedMember` was called with the correct base entity (`AspectBase<'C>`) but the override member from the intermediate class (`SelectionAspect<'Container>.Attach`). `getOverloadSuffixFrom` uses the entity's generic param names (`['C']`) as normalization keys, but the member's parameter types use the intermediate class's names (`'Container`), causing the lookup to fail and producing a different hash. Fix: after finding the base entity with `tryFindBaseEntity`, also find the corresponding dispatch slot member in that entity so the hash is computed with matching entity and member generic parameter names. Co-Authored-By: Claude Sonnet 4.6 --- src/Fable.Cli/CHANGELOG.md | 1 + src/Fable.Compiler/CHANGELOG.md | 1 + src/Fable.Transforms/FSharp2Fable.Util.fs | 27 ++++++++++++++------ tests/Js/Main/TypeTests.fs | 30 +++++++++++++++++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index f700a91832..d5f1434ffa 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 +* [JS/TS] Fix `super` call in multi-level generic class hierarchy generating wrong mangled method name (fix #3895) * [TS] Annotate `System.Collections.Generic.IList` as `MutableArray` (by @MangelMaxime) * [JS/TS] Fix `ResizeArray` index getter/setter not throwing `IndexOutOfRangeException` when index is out of bounds (fix #3812) (by @MangelMaxime) * [Beam] Fix unused term warning in try/catch when exception variable is not referenced (by @dbrattli) diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 41339e79c3..1774e58243 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 +* [JS/TS] Fix `super` call in multi-level generic class hierarchy generating wrong mangled method name (fix #3895) * [TS] Annotate `System.Collections.Generic.IList` as `MutableArray` (by @MangelMaxime) * [JS/TS] Fix `ResizeArray` index getter/setter not throwing `IndexOutOfRangeException` when index is out of bounds (fix #3812) (by @MangelMaxime) * [Beam] Fix unused term warning in try/catch when exception variable is not referenced (by @dbrattli) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index e0b2eeb620..9a2209bc99 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -2939,7 +2939,7 @@ module Util = // When calling `super` in an override, it may happen the method is not originally declared // by the immediate parent, so we need to go through the hierarchy until we find the original declaration // (this is important to get the correct mangled name) - let entity = + let entity, memb = match memb.IsOverrideOrExplicitInterfaceImplementation, callInfo.ThisArg with | true, Some(Fable.Value(Fable.BaseValue _, _)) -> // Only compare param types for overloads (single curried parameter group) @@ -2952,13 +2952,24 @@ module Util = else None - entity - |> tryFindBaseEntity (fun ent -> - tryFindAbstractMember com ent memb.CompiledName memb.IsInstanceMember paramTypes - |> Option.isSome - ) - |> Option.defaultValue entity - | _ -> entity + let baseEntity = + entity + |> tryFindBaseEntity (fun ent -> + tryFindAbstractMember com ent memb.CompiledName memb.IsInstanceMember paramTypes + |> Option.isSome + ) + |> Option.defaultValue entity + + // Also find the member from the base entity so the overload hash is computed + // with matching generic parameter names. When 'memb' is an override from a + // subclass with different generic param names than the base entity, the hash + // would be wrong if we use the override member with the base entity. (#3895) + let baseMember = + tryFindAbstractMember com baseEntity memb.CompiledName memb.IsInstanceMember paramTypes + |> Option.defaultValue memb + + baseEntity, baseMember + | _ -> entity, memb callAttachedMember com ctx r typ callInfo entity memb diff --git a/tests/Js/Main/TypeTests.fs b/tests/Js/Main/TypeTests.fs index 27c1c98ae2..e651133607 100644 --- a/tests/Js/Main/TypeTests.fs +++ b/tests/Js/Main/TypeTests.fs @@ -544,6 +544,30 @@ type MangledAbstractClass5(v) = type ConcreteClass1() = inherit MangledAbstractClass5(2) +// See #3895 - super call with generic class hierarchy uses wrong overload hash +type IGenericAttach3895<'C> = + abstract Attach: 'C -> unit + +[] +type GenericAttachBase3895<'C>() = + let mutable _baseCallCount = 0 + abstract Attach: 'C -> unit + default _.Attach(_owner: 'C) = _baseCallCount <- _baseCallCount + 1 + member _.BaseCallCount = _baseCallCount + + interface IGenericAttach3895<'C> with + member this.Attach(owner: 'C) = this.Attach(owner) + +type GenericAttachMid3895<'Container>() = + inherit GenericAttachBase3895<'Container>() + override this.Attach(owner) = + base.Attach(owner) + +type GenericAttachLeaf3895() = + inherit GenericAttachMid3895() + override this.Attach(owner) = + base.Attach(owner) + 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 @@ -1391,6 +1415,12 @@ let tests = let c = ConcreteClass1() c.MyMethod(4) |> equal 58 + // See #3895 - super call in generic class hierarchy was using wrong mangled name + testCase "Super call works correctly in multi-level generic class hierarchy" <| fun () -> + let obj = GenericAttachLeaf3895() + obj.Attach("hello") + obj.BaseCallCount |> equal 1 + // See #3328 testCase "SRTP works with byref" <| fun () -> let result = doubleIntByRef (TypeWithByRefMember()) 7