I'm trying to build a type provider for Bloomberg (they provide schema xml files that describe the types). I'm able to generate types that look like the following class:

type Example(x: option<int>) as self =
    [<DefaultValue>] val mutable X: option<int>

    do
        self.X <- x

    member this.GetX with get() = self.X

The problem is that this requires you to instantiate the example type as follows:

Example(Some 1)

Ideally I would prefer to instantiate it as like the XmlProvider choice type:

type Example(x: int) as self =
    [<DefaultValue>] val mutable X: option<int>

    do
        self.X <- Some x

    member this.GetX with get() = self.X

let example = Example(1)

When I attempt doing this:

let some =
    FSharpType.GetUnionCases(field.FieldType)
    |> Array.filter (fun x -> x.Name = "Some")
    |> Array.exactlyOne
let arg' = Expr.NewUnionCase(some, arg)
let setValue = Expr.FieldSet(this, field, arg')

I get the following error:

"Specified method is not supported."

I can successfully generate the first type using this:'

let setValue = Expr.FieldSet(this, field, arg)

The full source code is available here: https://github.com/alphaarchitect/BloombergProvider/blob/main/src/BloombergProvider.DesignTime/BloombergProvider.DesignTime.fs#L457

Any ideas?

///Edit

The entire code block is from this:

es
|> Result.mapError (fun xs -> ChoiceError.Element(c.Name, xs))
|> Result.map (fun xs ->
    xs
    |> List.iter (fun (_, field) ->
        let parameter = ProvidedParameter(field.Name, field.FieldType)
        providedChoiceType.AddMember
        <| ProvidedConstructor(
            [parameter],
            invokeCode =
                fun args ->
                    match args with
                    | this :: [arg] ->
                        let setValue = Expr.FieldSet(this, field, arg)
                        let enumField = Expr.FieldSet(this, enumField, Expr.FieldGet(enum.GetField(field.Name)))
                        Expr.Sequential(enumField, setValue)
                    | _ -> failwith "wrong ctor parameters"))
    {
        Enum = enum
        Object = providedChoiceType
    }))

to this:

es
|> Result.mapError (fun xs -> ChoiceError.Element(c.Name, xs))
|> Result.map (fun xs ->
    xs
    |> List.iter (fun (type', field) ->
        let parameter = ProvidedParameter(field.Name, type')
        providedChoiceType.AddMember
        <| ProvidedConstructor(
            [parameter],
            invokeCode =
                fun args ->
                    match args with
                    | this :: [arg] ->
                        let some =
                            FSharpType.GetUnionCases(field.FieldType)
                            |> Array.filter (fun x -> x.Name = "Some")
                            |> Array.exactlyOne
                        let arg' = Expr.NewUnionCase(some, arg)
                        let setValue = Expr.FieldSet(this, field, arg')
                        let enumField = Expr.FieldSet(this, enumField, Expr.FieldGet(enum.GetField(field.Name)))
                        Expr.Sequential(enumField, setValue)
                    | _ -> failwith "wrong ctor parameters"))
    {
        Enum = enum
        Object = providedChoiceType
    }))

Specified method is not supported.

The error occurs when you try to instantiate the type provider.

Running and evaluating the code works correctly in the quotation evaluator.

1

There are 1 best solutions below

1
On

This is not a direct answer to the question, but it may help you if you cannot figure out how to get this to work. In general, I think that building type providers is easier if you do not try to do a "lot of coding" in the provided methods and constructors.

In your approach, you have a type that inherits from obj and so you have to generate a lot of code to store data in fields. In contrast, the JSON provider types inherit from JsonValue which does all the storing for them - so they have to do relatively little. For example, say you want to generate something like:

type Example(x: int, y:string) as self =
  [<DefaultValue>] val mutable X: option<int>
  [<DefaultValue>] val mutable Y: option<string>
  do
    self.X <- Some x
    self.Y <- Some y
member this.GetX = self.X
member this.GetY = self.Y

It may be easier to define a base class which is essentially a dictionary an then generate a class inheriting from this:

type DataClass() = 
  let d = System.Collections.Generic.Dictionary<_, _>()
  member x.SetSome(s, v) = d.[s] <- Some v
  member x.Get<'T>(s) = d.[s] |> Option.map (fun v -> unbox<'T> v)

type Example(x: int, y:string) =
  inherit DataClass() 
  do
    base.SetSome("x", box x)
    base.SetSome("y", box y)
  member this.GetX = base.Get<int>("x")
  member this.GetY = base.Get<string>("y")

This way, the only kind of provided code that you have to write is method calls, which is generally easier to do - and possibly less error-prone.