Fable icon indicating copy to clipboard operation
Fable copied to clipboard

Error: FSharp.Reflection: Microsoft.FSharp.Core.FSharpOption`1 is not an F# union type

Open mlaily opened this issue 9 months ago • 2 comments

Hello,

I encountered a surprising error while using Fable.Elmish.Browser custom Traces.console:

Unable to process the message: [*removed*] Error: Microsoft.FSharp.Core.FSharpOption`1 is not an F# union type
    getUnionCases Reflection.js:310
    getCaseName Program.fs:25
    ElmishDebug_getMsgNameAndFields Program.fs:47
    program_4 Program.fs:54
    update program.fs:92
    [...]

And indeed the type object has undefined cases. Image

I verified in an F# interactive console and it does work as expected out of Fable:

> FSharp.Reflection.FSharpType.GetUnionCases(typeof<option<string>>);;
val it: Reflection.UnionCaseInfo array =
  [|FSharpOption`1.None
      {DeclaringType = Microsoft.FSharp.Core.FSharpOption`1[System.String];
       Name = "None";
       Tag = 0;};
    FSharpOption`1.Some
      {DeclaringType = Microsoft.FSharp.Core.FSharpOption`1[System.String];
       Name = "Some";
       Tag = 1;}|]

I added a unit test in Fable on my fork (see diff here) and it fails with the same error as above.

Image


Is it a known shortcoming of the reflection implementation in Fable that option types are not considered union cases?

Or is it a bug that needs fixing?

(I suspect it's a known shortcomings, because it seems too big to have been missed by inadvertence... but strangely I couldn't find an issue mentioning this?)

mlaily avatar Mar 16 '25 16:03 mlaily

Ah, actually the issue might not be where I though it was...

I'm still not sure whether it's normal that an option is not seen as a DU in Fable compiled code, but the root cause of my initial error seems to be a discrepancy in the behaviour of Microsoft.FSharp.Reflection.FSharpType methods vs Fable.Core.Reflection methods, probably due to the erasure of options.

The following code:

type MyUnion = | A of int | B of string

let testValue = Some (MyUnion.A 42)

Browser.Dom.console.log ({| TestValue = testValue |})
Browser.Dom.console.log ({| TestValueType = testValue.GetType() |})
Browser.Dom.console.log ({| IsUnionFS = Microsoft.FSharp.Reflection.FSharpType.IsUnion(testValue.GetType())|})
Browser.Dom.console.log ({| IsUnionFable = Fable.Core.Reflection.isUnion testValue |})
Browser.Dom.console.log ({| CaseNames = Fable.Core.Reflection.getCaseName testValue |})
Browser.Dom.console.log ({| CaseFields = Fable.Core.Reflection.getCaseFields testValue |})

Displays the following in a console:

Image

The union case seen by Fable.Core.Reflection is actually not the option, but the wrapped value! (which is itself a custom DU)

So mixing reflection calls from both namespaces at the same time seems very error prone...

EDIT: this has been fixed here https://github.com/elmish/browser/pull/77

I'm still curious about your expert opinion on this:

  • Is all the above expected?
  • Is there something we could improve? In documentation at least?
  • What's your recommendation regarding reflection in Fable? Prefer using Microsoft.FSharp.Reflection or Fable.Core.Reflection?

mlaily avatar Mar 16 '25 16:03 mlaily

Related https://github.com/fable-compiler/Fable/issues/2110

kerams avatar Mar 21 '25 15:03 kerams