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

Issue with sequence of non-erased type #352

Closed giacomociti closed 3 years ago

giacomociti commented 3 years ago

Description

I'm struggling to create a non-erased type with a member returning a sequence of another (or even the same) non-erased type

Repro steps

[<TypeProvider>]
type BasicGenerativeProvider (config : TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces (config, assemblyReplacementMap=[("LemonadeProvider.DesignTime", "LemonadeProvider.Runtime")])

    let ns = "LemonadeProvider"
    let asm = Assembly.GetExecutingAssembly()

    let createType typeName (count:int) =
        let asm = ProvidedAssembly()
        let myType = ProvidedTypeDefinition(asm, ns, typeName, Some typeof<obj>, isErased=false)

        let ctor = ProvidedConstructor([], invokeCode = fun _ -> <@@ () @@>)
        myType.AddMember(ctor)

        let resultType = ProvidedTypeBuilder.MakeGenericType(typedefof<seq<_>>, [myType])
        let lambda = Expr.Lambda(Var("_", typeof<int>), Expr.NewObject(ctor, []))
        let runtimeMethod = 
            let methodInfo = 
                match <@@ CommandRuntime.Map(id, []) @@> with
                | Patterns.Call(_, m, _) -> m
                | _ -> failwith "unexpected"
            ProvidedTypeBuilder.MakeGenericMethod(methodInfo.GetGenericMethodDefinition(), [myType])
        let integerSequence = <@@ Seq.init count id @@>
        let myProp = ProvidedProperty("foo", resultType, getterCode = function _ -> Expr.Call(runtimeMethod, [lambda ; integerSequence]))

        myType.AddMember myProp
        asm.AddTypes [ myType ]
        myType

    let myParamType = 
        let t = ProvidedTypeDefinition(asm, ns, "GenerativeProvider", Some typeof<obj>, isErased=false)
        t.DefineStaticParameters( [ProvidedStaticParameter("Count", typeof<int>)], fun typeName args -> createType typeName (unbox<int> args.[0]))
        t
    do
        this.AddNamespace(ns, [myParamType])

where in the runtime project I have the following helper method:

type CommandRuntime =
    static member Map(f, items: seq<int>) = Seq.map f items

Expected behavior

I expected the above code to create a working type provider:

type Gimme5 = LemonadeProvider.GenerativeProvider<5>

[<Test>]
let ``Can list`` () =
    let x: Gimme5 list = Gimme5().foo |> Seq.toList
    Assert.AreEqual(5, x.Length)

Actual behavior

The error raised is 'unexpected erased target ProvidedTypeDefinition' (thrown in ProvidedTypes.fs)

Known workarounds

It works adding a guard at line 8982: https://github.com/fsprojects/FSharp.TypeProviders.SDK/blob/1584152b527903e267f8f6f661c3c6f07659819e/src/ProvidedTypes.fs#L8982

if ptd.IsErased && ptd.BelongsToTargetModel then failwithf "unexpected erased target ProvidedTypeDefinition '%O'" ptd

and a similar one at line 9269 https://github.com/fsprojects/FSharp.TypeProviders.SDK/blob/1584152b527903e267f8f6f661c3c6f07659819e/src/ProvidedTypes.fs#L9269

 if x.IsErased && x.BelongsToTargetModel then failwithf "unexpected target type definition '%O'" x

but I'm a bit reluctant to propose such changes because I have no idea about their possible impacts.