Skip to content

Conversation

Programmerino
Copy link

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 (#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#.

…er constraints

This commit fixes an issue 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.

Changes:
- Add tryFindWitnessWithSourceTypes to properly select witnesses based on
  source type information when multiple witnesses match
- Update trait call resolution to use source types for disambiguation
- Fix generic argument composition in nested inline functions to preserve
  outer context mappings

The fix ensures that each generic parameter's static member constraints are
resolved to the correct witness, including in complex scenarios like nested
inline function calls.

Fixes the issue where code like:
```fsharp
let inline test<'a, 'b when 'a: (static member M: unit -> string)
                       and 'b: (static member M: unit -> string)> () =
    'a.M(), 'b.M()
```
Would incorrectly return the same value for both calls.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@MangelMaxime
Copy link
Member

Hello,

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

Added tests covering:
- Two and three parameter inline functions with static member constraints
- Multiple constraints per type parameter
- Reversed parameter order
- Nested inline function scenarios
- Various type parameter combinations

All tests pass successfully, verifying the fix for issue fable-compiler#4093.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@Programmerino
Copy link
Author

Let me know if that works!

@shayanhabibi
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants