Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion src/Fable.Transforms/FSharp2Fable.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,68 @@ module TypeHelpers =
&& listEquals (typeEquals false) argTypes w.ArgTypes
)

// Enhanced version that handles multiple witnesses for the same trait
// by using source type information to pick the correct one
let tryFindWitnessWithSourceTypes (ctx: Context) sourceTypes argTypes isInstance traitName =
let matchingWitnesses =
ctx.Witnesses
|> List.filter (fun w ->
w.TraitName = traitName
&& w.IsInstance = isInstance
&& listEquals (typeEquals false) argTypes w.ArgTypes
)

match matchingWitnesses with
| [] -> None
| [ single ] -> Some single
| multiple ->
// Multiple witnesses for the same trait - need to pick the right one
// based on which generic parameter is being resolved
match sourceTypes with
| [ sourceType ] ->
// First, resolve the source type in case it's a generic parameter
// that needs to be mapped to a concrete type
let resolvedSourceType =
match sourceType with
| Fable.GenericParam(name, _, _) ->
// Check if this generic parameter has been mapped to a concrete type
match Map.tryFind name ctx.GenericArgs with
| Some concreteType -> concreteType
| None -> sourceType
| _ -> sourceType

// Now find which witness to use based on the resolved source type
match resolvedSourceType with
| Fable.GenericParam(name, isMeasure, _) ->
// For generic parameters, find their position in the original function signature
let genParamNames = ctx.GenericArgs |> Map.toList |> List.map fst
let genParamPosition = genParamNames |> List.tryFindIndex ((=) name)

match genParamPosition with
| Some idx when idx < List.length multiple ->
// Witnesses are provided in order corresponding to generic parameters
List.tryItem idx multiple
| _ -> List.tryHead multiple

| Fable.DeclaredType(entity, _) ->
// Find the position of this entity type in the generic arguments
let genArgPosition =
ctx.GenericArgs
|> Map.toList
|> List.tryFindIndex (fun (_, argType) ->
match argType with
| Fable.DeclaredType(e, _) -> e.FullName = entity.FullName
| _ -> false
)

match genArgPosition with
| Some idx when idx < List.length multiple ->
// Witnesses are provided in order corresponding to generic parameters
List.tryItem idx multiple
| _ -> List.tryHead multiple
| _ -> List.tryHead multiple
| _ -> List.tryHead multiple

module Identifiers =
open Helpers
open TypeHelpers
Expand Down Expand Up @@ -2702,9 +2764,27 @@ module Util =

let genArgs = List.zipSafe inExpr.GenericArgs info.GenericArgs |> Map

// For nested inline functions, we need to preserve the outer context's generic args
// and resolve any references through them
let resolveTypeWithOuterContext (typ: Fable.Type) =
match typ with
| Fable.GenericParam(name, _, _) ->
// If this generic parameter is mapped in the outer context, use that
match Map.tryFind name ctx.GenericArgs with
| Some resolvedType -> resolvedType
| None -> typ
| _ -> typ

// Create the composed generic args by resolving through the outer context
let composedGenArgs =
genArgs
|> Map.map (fun _ typ -> resolveTypeWithOuterContext typ)
// Also preserve any generic args from the outer context that aren't overridden
|> Map.fold (fun acc k v -> Map.add k v acc) ctx.GenericArgs

let ctx =
{ ctx with
GenericArgs = genArgs
GenericArgs = composedGenArgs
InlinePath =
{
ToFile = inExpr.FileName
Expand Down
12 changes: 6 additions & 6 deletions src/Fable.Transforms/FSharp2Fable.fs
Original file line number Diff line number Diff line change
Expand Up @@ -923,9 +923,10 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs

return Fable.Unresolved(e, typ, r)
| None ->
match tryFindWitness ctx argTypes flags.IsInstance traitName with
let sourceTypes = List.map (makeType ctx.GenericArgs) sourceTypes

match tryFindWitnessWithSourceTypes ctx sourceTypes argTypes flags.IsInstance traitName with
| None ->
let sourceTypes = List.map (makeType ctx.GenericArgs) sourceTypes
return transformTraitCall com ctx r typ sourceTypes traitName flags.IsInstance argTypes argExprs
| Some w ->
let callInfo = makeCallInfo None argExprs argTypes
Expand Down Expand Up @@ -2454,11 +2455,10 @@ let resolveInlineExpr (com: IFableCompiler) ctx info expr =

let argExprs = argExprs |> List.map (resolveInlineExpr com ctx info)

match tryFindWitness ctx argTypes isInstance traitName with
| None ->
let sourceTypes = sourceTypes |> List.map (resolveInlineType ctx.GenericArgs)
let sourceTypes = sourceTypes |> List.map (resolveInlineType ctx.GenericArgs)

transformTraitCall com ctx r t sourceTypes traitName isInstance argTypes argExprs
match tryFindWitnessWithSourceTypes ctx sourceTypes argTypes isInstance traitName with
| None -> transformTraitCall com ctx r t sourceTypes traitName isInstance argTypes argExprs
| Some w ->
// As witnesses come from the context, idents may be duplicated, see #2855
let info =
Expand Down
76 changes: 76 additions & 0 deletions tests/Js/Main/TypeTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,50 @@ type StaticClass =
static member DefaultNullParam([<Optional; DefaultParameterValue(null:obj)>] x: obj) = x
static member inline InlineAdd(x: int, ?y: int) = x + (defaultArg y 2)

// Test types for generic parameter static member resolution
type TestTypeA =
static member GetValue() = "A"
static member Combine(x: int, y: int) = x + y + 100

type TestTypeB =
static member GetValue() = "B"
static member Combine(x: int, y: int) = x + y + 200

type TestTypeC =
static member GetValue() = "C"
static member Combine(x: int, y: int) = x + y + 300

// Inline functions for testing multiple generic parameters with static member constraints
let inline getTwoValues<'a, 'b when 'a: (static member GetValue: unit -> string)
and 'b: (static member GetValue: unit -> string)> () =
'a.GetValue(), 'b.GetValue()

let inline getThreeValues<'a, 'b, 'c when 'a: (static member GetValue: unit -> string)
and 'b: (static member GetValue: unit -> string)
and 'c: (static member GetValue: unit -> string)> () =
'a.GetValue(), 'b.GetValue(), 'c.GetValue()

let inline getValuesAndCombine<'a, 'b when 'a: (static member GetValue: unit -> string)
and 'a: (static member Combine: int * int -> int)
and 'b: (static member GetValue: unit -> string)
and 'b: (static member Combine: int * int -> int)> x y =
let aVal = 'a.GetValue()
let bVal = 'b.GetValue()
let aCombined = 'a.Combine(x, y)
let bCombined = 'b.Combine(x, y)
(aVal, aCombined), (bVal, bCombined)

let inline getReversed<'x, 'y when 'x: (static member GetValue: unit -> string)
and 'y: (static member GetValue: unit -> string)> () =
'y.GetValue(), 'x.GetValue()

let inline innerGet<'t when 't: (static member GetValue: unit -> string)> () =
't.GetValue()

let inline outerGet<'a, 'b when 'a: (static member GetValue: unit -> string)
and 'b: (static member GetValue: unit -> string)> () =
innerGet<'a>(), innerGet<'b>()

let tests =
testList "Types" [

Expand Down Expand Up @@ -1393,4 +1437,36 @@ let tests =
(upper :> IGenericMangledInterface<string>).ReadOnlyValue |> equal "value"
(upper :> IGenericMangledInterface<string>).SetterOnlyValue <- "setter only value"
(upper :> IGenericMangledInterface<string>).Value |> equal "setter only value"

// Test for generic type parameter static member resolution in inline functions
// https://github.com/fable-compiler/Fable/issues/4093
testCase "Inline function with two generic parameters resolves static members correctly" <| fun () ->
let result = getTwoValues<TestTypeA, TestTypeB>()
result |> equal ("A", "B")

testCase "Inline function with three generic parameters resolves static members correctly" <| fun () ->
let result = getThreeValues<TestTypeA, TestTypeB, TestTypeC>()
result |> equal ("A", "B", "C")

testCase "Inline function with multiple constraints per type parameter works" <| fun () ->
let result = getValuesAndCombine<TestTypeA, TestTypeB> 10 20
result |> equal (("A", 130), ("B", 230))

testCase "Inline function with reversed type parameter order works" <| fun () ->
let result = getReversed<TestTypeA, TestTypeB>()
result |> equal ("B", "A")

testCase "Nested inline functions resolve generic parameters correctly" <| fun () ->
let result = outerGet<TestTypeA, TestTypeB>()
result |> equal ("A", "B")

testCase "Different type parameter combinations work correctly" <| fun () ->
let result1 = getTwoValues<TestTypeB, TestTypeA>()
result1 |> equal ("B", "A")

let result2 = getTwoValues<TestTypeC, TestTypeA>()
result2 |> equal ("C", "A")

let result3 = getTwoValues<TestTypeB, TestTypeC>()
result3 |> equal ("B", "C")
]
Loading