fsprojects / FSharp.TypeProviders.SDK

The SDK for creating F# type providers
https://fsprojects.github.io/FSharp.TypeProviders.SDK/
MIT License
298 stars 94 forks source link

Wrapping a constructor parameter with Option.Some before setting an optional field throws method not supported error. #366

Open bdkoepke opened 2 years ago

bdkoepke commented 2 years ago

Description

I'm building a type provider for Bloomberg (they have xml based schema files that describe the entire API). The code-generation part of the API is pretty close to working (there are four primary different types: enums, records, unions, and operations). For the implementation of the record type and union types, I'm trying to improve the API so 'optional' parameters can be specified as the value directly rather than (Some t'). When I change the code from the following:

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

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
    }))

Then I get the error "Specified method is not supported."

The key part is really just the following:

from

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

to

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')

Wrapping the argument in 'Some' seems to be causing this error.

Expected behavior

I'm trying to generate a type like the following:

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

    do
        self.X <- Some x

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

Such that the argument is not an "optional" type but the backing field is.

Actual behavior

I get the error "Specified method is not supported."

image

Known workarounds

I have this working:

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

    do
        self.X <- x

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

But that sucks because now you can only instantiate the Example object by doing:

Example(Some 1)

Rather than

Example(1)

Thank you!

Related information

dotnet 3.1

dsyme commented 2 years ago

@bdkoepke I'm only just catching up on this issue

Did you find an adequate workaround? Also, please feel free to ask further questions or start discussions here.

bdkoepke commented 1 year ago

Hello, apologies it took so long to respond. No workaround yet.

The code is here: https://github.com/alphaarchitect/BloombergProvider