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

Newbie help - Can't add ProvidedMethod #219

Closed davidtme closed 6 years ago

davidtme commented 6 years ago

I'm starting out with the ComboProvider examples but i'm having trouble adding a ProvidedMethod.

I'm using github fsprojects/FSharp.TypeProviders.SDK src/ProvidedTypes.fs in my paket file

I can't seem to get passed this error:

1>C:\xxx\Test.fs(5,14): error FS3033: The type provider 'ProviderImplementation.ComboGenerativeProvider' reported an error: Type mismatch when splicing expression into quotation literal. The type of the expression tree being inserted doesn't match the type expected by the splicing operation. Expected 'Type1', but received type 'System.Object'. Consider type-annotating with the expected expression type, e.g., (%% x : Type1) or (%x : Type1). Parameter name: receivedType

I know it's me being silly but any help would be really useful.

Provider code

namespace ProviderImplementation

open ProviderImplementation
open ProviderImplementation.ProvidedTypes
open FSharp.Quotations
open FSharp.Core.CompilerServices
open System.Reflection

[<TypeProvider>]
type ComboGenerativeProvider (config : TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces (config)

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

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

        let ctor = 
            ProvidedConstructor(
                [ ProvidedParameter("value", typeof<string>) ], 
                fun args -> <@@ (%%(args.[1]) : string) :> obj @@>)

        myType.AddMember(ctor)

        // Without this block the test project compiles. 
        let prop = 
            ProvidedMethod(
                "Value", 
                [],
                typeof<string>, 
                fun args -> <@@ (%%(args.[0]) : obj) :?> string @@>)

        myType.AddMember prop

        asm.AddTypes [ myType ]

        myType

    let myParamType = 
        let t = ProvidedTypeDefinition(asm, ns, "GenerativeProvider", Some typeof<obj>, isErased=false)
        t.DefineStaticParameters( [ProvidedStaticParameter("CommandText", typeof<string>)], createType)
        t
    do
        this.AddNamespace(ns, [myParamType])

[<assembly:CompilerServices.TypeProviderAssembly()>]
do ()

Test Code

module Test

open ComboProvider

type Type1 = ComboProvider.GenerativeProvider<CommandText = "hello world">

[<EntryPoint>]
let main args = 
    let ab = Type1("")

    //let a = ab.Value()

    0
baronfel commented 6 years ago

The arg list for a provided method is of the form [thisArg;...methodArgs], so args.[0] is the this argument, which has type Type1, and so when you declare that this is of type obj the compiler wants to disagree with you!

In fact, in a lot of type provider code, I see explicit matching on the args parameter of invokeCode definitions like so: fun [thisArg;param1;param2] -> // do things, even though it's a partial pattern.

davidtme commented 6 years ago

Thanks @baronfel for the fast response, how do I access the internal value set in the ProvidedConstructor i.e. <@@ (%%(args.[1]) : string) :> obj @@>, reading the docs I thought this would be first argument passed into ProvidedMethod?

baronfel commented 6 years ago

So you've got to define a kind of runtime container that you can put data passed in via your constructor into. This gives you a 'name' for the type that you can coerce your this parameter to, and then access that stored data in your provided methods.

A good example is the swagger provider. Here a tiny runtime type is defined that captures all of the data passed in via constructor parameters. Once that type is defined, here we set it as the base type for our generated types, and then in the invokeCode members of our provided properties (eg here) we can then coerce the this argument to that base type and access its properties.

So long story short I think you need to do the same. Here's how we write that constructor.

davidtme commented 6 years ago

@baronfel thanks for all your help :)