Fable icon indicating copy to clipboard operation
Fable copied to clipboard

Fix generic parameter resolution in inline functions with static member constraints

Open Programmerino opened this issue 6 months ago • 3 comments

Fix generic parameter resolution in inline functions with static member constraints

Note: Claude Code did this for me, so it could be terrible

Description

This PR fixes a bug (https://github.com/fable-compiler/Fable/issues/4093) where inline functions with multiple generic type parameters and static member constraints would incorrectly resolve all trait calls to the first type parameter's witness.

The Issue

When using inline functions with multiple generic parameters that have static member constraints, all static member calls would resolve to the first type parameter:

type A = static member M() = "A"
type B = static member M() = "B"

let inline test<'a, 'b when 'a: (static member M: unit -> string) 
                       and 'b: (static member M: unit -> string)> () =
    'a.M(), 'b.M()  // Both calls incorrectly returned "A"

test<A, B>()  // Would return ("A", "A") instead of ("A", "B")

Root Cause

The issue occurred in two places:

  1. During trait call resolution, when multiple witnesses matched the same trait, the code always selected the first one
  2. For nested inline functions, generic parameter mappings were being replaced instead of composed

Solution

  1. Enhanced Witness Resolution: Added tryFindWitnessWithSourceTypes that uses source type information to select the correct witness when multiple witnesses match. It determines which generic parameter is being resolved and selects the corresponding witness based on position.

  2. Fixed Nested Inline Functions: Modified the generic argument handling in inlineExpr to compose mappings rather than replace them, ensuring that inner inline functions can resolve their generic parameters through the outer function's context.

Changes

  • Added tryFindWitnessWithSourceTypes in FSharp2Fable.Util.fs to handle witness disambiguation
  • Updated trait call resolution in FSharp2Fable.fs to use source types for witness selection
  • Fixed generic argument composition for nested inline functions to preserve outer context mappings

Testing

The fix has been tested with:

  • Basic two-parameter inline functions
  • Three or more parameter inline functions
  • Different parameter orders
  • Multiple constraints per type
  • Nested inline function calls
  • Various type combinations

All scenarios now work correctly.

Impact

This fix enables proper use of inline functions with multiple generic parameters having static member constraints, which is important for generic programming patterns in F#.

Programmerino avatar Jul 03 '25 05:07 Programmerino

Hello,

Can you please add some test suits so we can check if the behavior is correct ?

MangelMaxime avatar Jul 03 '25 13:07 MangelMaxime

Let me know if that works!

Programmerino avatar Jul 03 '25 22:07 Programmerino

Not going to lie, I really didn't think Claude could have pulled that off. Actually works. 👀. Nice!

shayanhabibi avatar Jul 05 '25 02:07 shayanhabibi